{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7130b96b-92c7-4497-bc0d-d63c156e84d2",
   "metadata": {},
   "source": [
    "### 导入数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "0fe6af3d-449a-40a0-ab21-bfb53c1ff481",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "18f14c25-ff8d-4adf-9c02-cd51e7999a2f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>TICKER_SYMBOL</th>\n",
       "      <th>ACT_PUBTIME</th>\n",
       "      <th>PUBLISH_DATE</th>\n",
       "      <th>END_DATE_REP</th>\n",
       "      <th>END_DATE</th>\n",
       "      <th>REPORT_TYPE</th>\n",
       "      <th>FISCAL_PERIOD</th>\n",
       "      <th>MERGED_FLAG</th>\n",
       "      <th>ACCOUTING_STANDARDS</th>\n",
       "      <th>CURRENCY_CD</th>\n",
       "      <th>...</th>\n",
       "      <th>CA_TURNOVER</th>\n",
       "      <th>OPER_CYCLE</th>\n",
       "      <th>INVEN_TURNOVER</th>\n",
       "      <th>FA_TURNOVER</th>\n",
       "      <th>TFA_TURNOVER</th>\n",
       "      <th>DAYS_AP</th>\n",
       "      <th>DAYS_INVEN</th>\n",
       "      <th>TA_TURNOVER</th>\n",
       "      <th>AR_TURNOVER</th>\n",
       "      <th>FLAG</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>4019</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>8166</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>9063</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>1.3751</td>\n",
       "      <td>148.8938</td>\n",
       "      <td>7.424</td>\n",
       "      <td>20.9362</td>\n",
       "      <td>15.298</td>\n",
       "      <td>75.4337</td>\n",
       "      <td>48.4911</td>\n",
       "      <td>1.2774</td>\n",
       "      <td>3.5856</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>10083</td>\n",
       "      <td>4</td>\n",
       "      <td>4</td>\n",
       "      <td>4</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>11737</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 363 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "   TICKER_SYMBOL  ACT_PUBTIME  PUBLISH_DATE  END_DATE_REP  END_DATE  \\\n",
       "0           4019            3             3             2         1   \n",
       "1           8166            3             3             2         1   \n",
       "2           9063            3             3             2         1   \n",
       "3          10083            4             4             4         1   \n",
       "4          11737            3             3             2         1   \n",
       "\n",
       "  REPORT_TYPE  FISCAL_PERIOD  MERGED_FLAG ACCOUTING_STANDARDS CURRENCY_CD  \\\n",
       "0           A             12            1           CHAS_2007         CNY   \n",
       "1           A             12            1           CHAS_2007         CNY   \n",
       "2           A             12            1           CHAS_2007         CNY   \n",
       "3           A             12            1           CHAS_2007         CNY   \n",
       "4           A             12            1           CHAS_2007         CNY   \n",
       "\n",
       "   ...  CA_TURNOVER  OPER_CYCLE  INVEN_TURNOVER  FA_TURNOVER  TFA_TURNOVER  \\\n",
       "0  ...          NaN         NaN             NaN          NaN           NaN   \n",
       "1  ...          NaN         NaN             NaN          NaN           NaN   \n",
       "2  ...       1.3751    148.8938           7.424      20.9362        15.298   \n",
       "3  ...          NaN         NaN             NaN          NaN           NaN   \n",
       "4  ...          NaN         NaN             NaN          NaN           NaN   \n",
       "\n",
       "   DAYS_AP  DAYS_INVEN  TA_TURNOVER  AR_TURNOVER  FLAG  \n",
       "0      NaN         NaN          NaN          NaN   0.0  \n",
       "1      NaN         NaN          NaN          NaN   0.0  \n",
       "2  75.4337     48.4911       1.2774       3.5856   0.0  \n",
       "3      NaN         NaN          NaN          NaN   0.0  \n",
       "4      NaN         NaN          NaN          NaN   0.0  \n",
       "\n",
       "[5 rows x 363 columns]"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data = pd.read_csv(r'C:\\Users\\mwj\\Desktop\\01-财务造假\\5. 各公司财务报告.csv')\n",
    "data.head(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "fe2a289f-2b75-4d78-9d90-1a0662abafe0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>count</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>所属行业</th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>制造业</th>\n",
       "      <td>2667</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>信息传输、软件和信息技术服务业</th>\n",
       "      <td>343</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>批发和零售业</th>\n",
       "      <td>170</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>金融业</th>\n",
       "      <td>121</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>房地产业</th>\n",
       "      <td>120</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>电力、热力、燃气及水生产和供应业</th>\n",
       "      <td>118</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>交通运输、仓储和邮政业</th>\n",
       "      <td>107</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>建筑业</th>\n",
       "      <td>98</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>采矿业</th>\n",
       "      <td>77</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>水利、环境和公共设施管理业</th>\n",
       "      <td>71</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>科学研究和技术服务业</th>\n",
       "      <td>64</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>文化、体育和娱乐业</th>\n",
       "      <td>59</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>租赁和商务服务业</th>\n",
       "      <td>58</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>农、林、牧、渔业</th>\n",
       "      <td>42</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>综合</th>\n",
       "      <td>16</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>卫生和社会工作</th>\n",
       "      <td>12</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>住宿和餐饮业</th>\n",
       "      <td>10</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>教育</th>\n",
       "      <td>9</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>居民服务、修理和其他服务业</th>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                  count\n",
       "所属行业                   \n",
       "制造业                2667\n",
       "信息传输、软件和信息技术服务业     343\n",
       "批发和零售业              170\n",
       "金融业                 121\n",
       "房地产业                120\n",
       "电力、热力、燃气及水生产和供应业    118\n",
       "交通运输、仓储和邮政业         107\n",
       "建筑业                  98\n",
       "采矿业                  77\n",
       "水利、环境和公共设施管理业        71\n",
       "科学研究和技术服务业           64\n",
       "文化、体育和娱乐业            59\n",
       "租赁和商务服务业             58\n",
       "农、林、牧、渔业             42\n",
       "综合                   16\n",
       "卫生和社会工作              12\n",
       "住宿和餐饮业               10\n",
       "教育                    9\n",
       "居民服务、修理和其他服务业         1"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "industry = pd.read_excel(r'C:\\Users\\mwj\\Desktop\\01-财务造假\\5. 公司对应行业.xlsx')\n",
    "industry.head(5)\n",
    "industry['所属行业'].value_counts()\n",
    "\n",
    "pd.DataFrame(industry['所属行业'].value_counts())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ef0c7ef9-07a5-48cf-9106-f763d9d645a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 重命名列：为了保证表合并的时候列名一致\n",
    "industry.rename(columns={'股票代码': 'TICKER_SYMBOL'}, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "2b431faf-1e18-4ed1-8a70-cbea2b3b0f54",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>TICKER_SYMBOL</th>\n",
       "      <th>所属行业</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>4019</td>\n",
       "      <td>制造业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>4213</td>\n",
       "      <td>制造业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>8166</td>\n",
       "      <td>制造业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>9063</td>\n",
       "      <td>批发和零售业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>10083</td>\n",
       "      <td>信息传输、软件和信息技术服务业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4158</th>\n",
       "      <td>4993201</td>\n",
       "      <td>制造业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4159</th>\n",
       "      <td>4993297</td>\n",
       "      <td>电力、热力、燃气及水生产和供应业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4160</th>\n",
       "      <td>4997833</td>\n",
       "      <td>科学研究和技术服务业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4161</th>\n",
       "      <td>4998808</td>\n",
       "      <td>制造业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4162</th>\n",
       "      <td>4999709</td>\n",
       "      <td>制造业</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>4163 rows × 2 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "      TICKER_SYMBOL              所属行业\n",
       "0              4019               制造业\n",
       "1              4213               制造业\n",
       "2              8166               制造业\n",
       "3              9063            批发和零售业\n",
       "4             10083   信息传输、软件和信息技术服务业\n",
       "...             ...               ...\n",
       "4158        4993201               制造业\n",
       "4159        4993297  电力、热力、燃气及水生产和供应业\n",
       "4160        4997833        科学研究和技术服务业\n",
       "4161        4998808               制造业\n",
       "4162        4999709               制造业\n",
       "\n",
       "[4163 rows x 2 columns]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# # 附件1中的股票代码类型为浮点数，为了后面的连接操作，这里需要转换为整数型\n",
    "industry['TICKER_SYMBOL'] = industry['TICKER_SYMBOL'].astype(int)\n",
    "industry"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "17ac6c08-14d8-4f49-964c-dc178234f5cb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>TICKER_SYMBOL</th>\n",
       "      <th>ACT_PUBTIME</th>\n",
       "      <th>PUBLISH_DATE</th>\n",
       "      <th>END_DATE_REP</th>\n",
       "      <th>END_DATE</th>\n",
       "      <th>REPORT_TYPE</th>\n",
       "      <th>FISCAL_PERIOD</th>\n",
       "      <th>MERGED_FLAG</th>\n",
       "      <th>ACCOUTING_STANDARDS</th>\n",
       "      <th>CURRENCY_CD</th>\n",
       "      <th>...</th>\n",
       "      <th>OPER_CYCLE</th>\n",
       "      <th>INVEN_TURNOVER</th>\n",
       "      <th>FA_TURNOVER</th>\n",
       "      <th>TFA_TURNOVER</th>\n",
       "      <th>DAYS_AP</th>\n",
       "      <th>DAYS_INVEN</th>\n",
       "      <th>TA_TURNOVER</th>\n",
       "      <th>AR_TURNOVER</th>\n",
       "      <th>FLAG</th>\n",
       "      <th>所属行业</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>4019</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "      <td>制造业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>8166</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "      <td>制造业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>9063</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>148.8938</td>\n",
       "      <td>7.424</td>\n",
       "      <td>20.9362</td>\n",
       "      <td>15.298</td>\n",
       "      <td>75.4337</td>\n",
       "      <td>48.4911</td>\n",
       "      <td>1.2774</td>\n",
       "      <td>3.5856</td>\n",
       "      <td>0.0</td>\n",
       "      <td>批发和零售业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>10083</td>\n",
       "      <td>4</td>\n",
       "      <td>4</td>\n",
       "      <td>4</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "      <td>信息传输、软件和信息技术服务业</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>11737</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>A</td>\n",
       "      <td>12</td>\n",
       "      <td>1</td>\n",
       "      <td>CHAS_2007</td>\n",
       "      <td>CNY</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "      <td>制造业</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 364 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "   TICKER_SYMBOL  ACT_PUBTIME  PUBLISH_DATE  END_DATE_REP  END_DATE  \\\n",
       "0           4019            3             3             2         1   \n",
       "1           8166            3             3             2         1   \n",
       "2           9063            3             3             2         1   \n",
       "3          10083            4             4             4         1   \n",
       "4          11737            3             3             2         1   \n",
       "\n",
       "  REPORT_TYPE  FISCAL_PERIOD  MERGED_FLAG ACCOUTING_STANDARDS CURRENCY_CD  \\\n",
       "0           A             12            1           CHAS_2007         CNY   \n",
       "1           A             12            1           CHAS_2007         CNY   \n",
       "2           A             12            1           CHAS_2007         CNY   \n",
       "3           A             12            1           CHAS_2007         CNY   \n",
       "4           A             12            1           CHAS_2007         CNY   \n",
       "\n",
       "   ...  OPER_CYCLE  INVEN_TURNOVER  FA_TURNOVER  TFA_TURNOVER  DAYS_AP  \\\n",
       "0  ...         NaN             NaN          NaN           NaN      NaN   \n",
       "1  ...         NaN             NaN          NaN           NaN      NaN   \n",
       "2  ...    148.8938           7.424      20.9362        15.298  75.4337   \n",
       "3  ...         NaN             NaN          NaN           NaN      NaN   \n",
       "4  ...         NaN             NaN          NaN           NaN      NaN   \n",
       "\n",
       "   DAYS_INVEN  TA_TURNOVER  AR_TURNOVER  FLAG             所属行业  \n",
       "0         NaN          NaN          NaN   0.0              制造业  \n",
       "1         NaN          NaN          NaN   0.0              制造业  \n",
       "2     48.4911       1.2774       3.5856   0.0           批发和零售业  \n",
       "3         NaN          NaN          NaN   0.0  信息传输、软件和信息技术服务业  \n",
       "4         NaN          NaN          NaN   0.0              制造业  \n",
       "\n",
       "[5 rows x 364 columns]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 数据合并  -  左合并\n",
    "data_merge = pd.merge(data, industry, how='left', on=['TICKER_SYMBOL'])\n",
    "data_merge.head(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7d891fe2-10e2-45a3-97ec-2f86f1d268eb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "所属行业\n",
       "制造业                 13131\n",
       "信息传输、软件和信息技术服务业      1689\n",
       "批发和零售业                930\n",
       "电力、热力、燃气及水生产和供应业      634\n",
       "房地产业                  631\n",
       "金融业                   597\n",
       "交通运输、仓储和邮政业           571\n",
       "建筑业                   497\n",
       "采矿业                   435\n",
       "水利、环境和公共设施管理业         342\n",
       "文化、体育和娱乐业             327\n",
       "租赁和商务服务业              302\n",
       "科学研究和技术服务业            285\n",
       "农、林、牧、渔业              239\n",
       "综合                     96\n",
       "卫生和社会工作                71\n",
       "住宿和餐饮业                 56\n",
       "教育                     50\n",
       "居民服务、修理和其他服务业           5\n",
       "Name: count, dtype: int64"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data_merge['所属行业'].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "46ae1752-ff46-4c4e-81db-8950b5880e12",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "所属行业\n",
       "制造业    13131\n",
       "Name: count, dtype: int64"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 提取【制造业】的企业的数据\n",
    "datas_manual = data_merge[data_merge['所属行业']=='制造业']\n",
    "datas_manual['所属行业'].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "960768cd-0287-4e68-9556-28f63fc0a746",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "END_DATE\n",
       "5    2500\n",
       "4    2242\n",
       "3    2094\n",
       "2    2031\n",
       "1    1764\n",
       "Name: count, dtype: int64"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "END_DATE\n",
       "6    2500\n",
       "Name: count, dtype: int64"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 筛选 训练的数据(前五年) 和 最终预测的数据(第六年)\n",
    "datas_manual_y5 = datas_manual[datas_manual['END_DATE'] != 6]\n",
    "datas_manual_y6 = datas_manual[datas_manual['END_DATE'] == 6]\n",
    "display(datas_manual_y5['END_DATE'].value_counts())\n",
    "display(datas_manual_y6['END_DATE'].value_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f8c0c73-a8a0-4ad2-afab-66f31a751226",
   "metadata": {},
   "source": [
    "### 数据预处理"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1bacff96-dee8-48e0-8bdf-7d5baf3e4597",
   "metadata": {},
   "source": [
    "1. 数据清洗："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "611941c9-ab74-415e-85aa-404b68758c86",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 去除只有一个值的特征\n",
    "single_value_columns = []\n",
    "for column in datas_manual_y5.columns:\n",
    "    if datas_manual_y5[column].nunique() == 1:\n",
    "        single_value_columns.append(column)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "17c92704-6838-49b6-b10c-989c0bdbcfb0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# single_value_columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "c4a33755-90df-42de-b75f-89d81d3d86b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "datas_manual_y5 = datas_manual_y5.drop(columns= single_value_columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19f56e81-fb5a-439c-ae64-56a4522bb0dd",
   "metadata": {},
   "source": [
    "2. 缺失值处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "c811c27e-6ebc-4673-8068-5b9e29a2d9dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 缺失值处理 思路 \n",
    "# 1：缺失率大于 75% 的样本 -行\n",
    "datas_manual_y5 = datas_manual_y5[datas_manual_y5.isnull().mean(axis = 1) <= 0.70]\n",
    "# 2: 缺失率大于 75% 的特征 -列\n",
    "datas_manual_y5 = datas_manual_y5.loc[:, datas_manual_y5.isnull().mean() <= 0.70]\n",
    "# 3：缺失值填充 - 中位数/均值/插值法（如随机森林）等\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "bb077eda-cdd2-49c1-b8fe-67f854cbe857",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "# plt.hist(datas_manual_y5.isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "d51fa32c-4686-4324-a870-169e9ed5e9c6",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "datas_filled = datas_manual_y5.copy()\n",
    "\n",
    "# 对数值型列使用中位数插值\n",
    "numeric_columns = datas_filled.select_dtypes(include=[np.number]).columns\n",
    "\n",
    "for col in numeric_columns:\n",
    "    median_value = datas_filled[col].median()\n",
    "    datas_filled[col] = datas_filled[col].fillna(median_value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "22660a1a-3ee1-4dcc-8d05-6f0b290b3931",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "TICKER_SYMBOL    0\n",
       "ACT_PUBTIME      0\n",
       "PUBLISH_DATE     0\n",
       "END_DATE_REP     0\n",
       "END_DATE         0\n",
       "                ..\n",
       "DAYS_AP          0\n",
       "DAYS_INVEN       0\n",
       "TA_TURNOVER      0\n",
       "AR_TURNOVER      0\n",
       "FLAG             0\n",
       "Length: 272, dtype: int64"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "datas_filled.isnull().sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1100a62-e8a6-4adf-aec2-0133e9fb4368",
   "metadata": {},
   "source": [
    "3. 异常值处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1c7832d9-091c-427d-9928-8501fb560ad5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 异常值处理 \n",
    "# 1. 可以选择不做处理\n",
    "# 2. 箱线图分析"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55f9d85f-228c-4042-95c4-854b4092e8e6",
   "metadata": {},
   "source": [
    "4. 划分数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "57e583d6-1d0f-4f50-82e8-d1c6801bcb25",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "data_x = datas_filled.iloc[:,:-1]\n",
    "data_y = datas_filled.iloc[:,-1]\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(data_x, data_y, \n",
    "                                                    test_size=0.33, random_state=42)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f3035f3-a9b6-4514-a46f-a379c0d86f93",
   "metadata": {},
   "source": [
    "5. 标准化处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "90ea38cc-4b16-462a-8b0c-dd25029e342c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(3729, 271)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "(1837, 271)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "# 创建标准化器\n",
    "scaler = StandardScaler()\n",
    "\n",
    "# 只在训练集上拟合（计算均值和标准差）\n",
    "scaler.fit(X_train)\n",
    "\n",
    "# 用训练集的统计量转换训练集和测试集\n",
    "X_train_scaled = scaler.transform(X_train)\n",
    "X_train_scaled = pd.DataFrame(X_train_scaled, index = X_train.index, columns = X_train.columns)\n",
    "display(X_train_scaled.shape)\n",
    "\n",
    "X_test_scaled = scaler.transform(X_test)\n",
    "X_test_scaled = pd.DataFrame(X_test_scaled, index = X_test.index, columns = X_test.columns)\n",
    "display(X_test_scaled.shape)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a915bf6-82de-4fc9-9510-5c252a63c2ab",
   "metadata": {},
   "source": [
    "### 特征工程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "ed71bd96-e5b6-4cdf-8724-e2589f8258a0",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "基于互信息累计贡献度（80%），选择Top 92特征\n",
      "删除低重要性高VIF特征: T_LIAB_EQUITY, VIF=2208587.20\n",
      "删除低重要性高VIF特征: TEAP_TA, VIF=1550123.69\n",
      "删除低重要性高VIF特征: N_TAN_A_TA, VIF=204224.26\n",
      "删除低重要性高VIF特征: REVENUE, VIF=65245.45\n",
      "删除低重要性高VIF特征: T_COGS, VIF=1747.41\n",
      "删除低重要性高VIF特征: C_INF_FR_OPERATE_A, VIF=1597.62\n",
      "删除低重要性高VIF特征: C_OUTF_OPERATE_A, VIF=595.36\n",
      "删除低重要性高VIF特征: T_REVENUE, VIF=490.20\n",
      "删除低重要性高VIF特征: T_ASSETS, VIF=387.88\n",
      "删除低重要性高VIF特征: C_FR_SALE_G_S, VIF=168.71\n",
      "删除低重要性高VIF特征: N_INCOME, VIF=161.70\n",
      "删除低重要性高VIF特征: T_SH_EQUITY, VIF=115.97\n",
      "删除低重要性高VIF特征: T_CA, VIF=57.22\n",
      "删除低重要性高VIF特征: OPERATE_PROFIT, VIF=43.77\n",
      "删除低重要性高VIF特征: COGS, VIF=42.06\n",
      "删除低重要性高VIF特征: T_LIAB, VIF=30.60\n",
      "删除低重要性高VIF特征: C_FR_BORR, VIF=28.51\n",
      "删除低重要性高VIF特征: NCL_TA, VIF=26.24\n",
      "删除低重要性高VIF特征: CASH_C_EQUIV, VIF=26.01\n",
      "删除低重要性高VIF特征: C_PAID_FOR_TAXES, VIF=17.82\n",
      "删除低重要性高VIF特征: BASIC_EPS, VIF=15.05\n",
      "删除低重要性高VIF特征: T_EQUITY_ATTR_P, VIF=14.43\n",
      "删除低重要性高VIF特征: TP_TR, VIF=11.74\n",
      "最终特征数: 69\n",
      "最终特征数: 69\n",
      "特征工程后训练集形状: (3729, 69)\n",
      "特征工程后测试集形状: (1837, 69)\n",
      "特征名称: ['TICKER_SYMBOL', 'END_DATE_REP', 'AR', 'INT_RECEIV', 'INVENTORIES', 'AVAIL_FOR_SALE_FA', 'LT_EQUITY_INVEST', 'FIXED_ASSETS', 'CIP', 'INTAN_ASSETS']...\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from sklearn.feature_selection import mutual_info_classif, SelectKBest, VarianceThreshold\n",
    "from sklearn.decomposition import PCA\n",
    "from statsmodels.stats.outliers_influence import variance_inflation_factor\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 1. 异常值处理（减少极端值影响）\n",
    "# --------------------------\n",
    "def handle_outliers(df, train_means, train_stds, threshold=3):\n",
    "    \"\"\"用训练集的均值和标准差处理异常值（替换为边界值）\"\"\"\n",
    "    df_clean = df.copy()\n",
    "    for col in df_clean.columns:\n",
    "        if col not in train_means:\n",
    "            continue  # 确保只处理训练集中存在的特征\n",
    "        mean = train_means[col]\n",
    "        std = train_stds[col]\n",
    "        # 计算Z-score（基于训练集分布）\n",
    "        upper_bound = mean + threshold * std\n",
    "        lower_bound = mean - threshold * std\n",
    "        df_clean[col] = np.where(df_clean[col] > upper_bound, upper_bound, df_clean[col])\n",
    "        df_clean[col] = np.where(df_clean[col] < lower_bound, lower_bound, df_clean[col])\n",
    "    return df_clean\n",
    "\n",
    "# 计算训练集的均值和标准差（用于测试集和未来数据）\n",
    "train_means = X_train_scaled.mean()\n",
    "train_stds = X_train_scaled.std()\n",
    "\n",
    "# 用训练集的分布处理训练集和测试集\n",
    "X_train_clean = handle_outliers(X_train_scaled, train_means, train_stds)\n",
    "X_test_clean = handle_outliers(X_test_scaled, train_means, train_stds)  # 测试集复用训练集分布\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 2. 特征选择（去除低重要性特征）\n",
    "# --------------------------\n",
    "# 2.1 过滤法：基于互信息选择Top N特征（互信息衡量特征与目标的非线性相关性）\n",
    "mi = mutual_info_classif(X_train_clean, y_train)\n",
    "mi_series = pd.Series(mi, index=X_train_clean.columns, name='mutual_info').sort_values(ascending=False)\n",
    "\n",
    "# 动态确定Top K：选择累计互信息占比达80%的特征（避免固定值）\n",
    "cumulative_mi = mi_series.cumsum() / mi_series.sum()\n",
    "top_k = np.argmax(cumulative_mi >= 0.8) + 1  # 找到第一个累计占比≥80%的位置\n",
    "top_k = max(top_k, 5)  # 确保至少保留5个特征\n",
    "print(f\"基于互信息累计贡献度（80%），选择Top {top_k}特征\")\n",
    "\n",
    "selector = SelectKBest(mutual_info_classif, k=top_k)\n",
    "X_train_selected = selector.fit_transform(X_train_clean, y_train)\n",
    "X_test_selected = selector.transform(X_test_clean)\n",
    "\n",
    "# 提取选择后的特征名\n",
    "selected_cols = X_train_clean.columns[selector.get_support()]\n",
    "X_train_selected = pd.DataFrame(X_train_selected, columns=selected_cols, index=X_train_clean.index)\n",
    "X_test_selected = pd.DataFrame(X_test_selected, columns=selected_cols, index=X_test_clean.index)\n",
    "\n",
    "\n",
    "# 2.2 去除高共线性特征（用VIF检测多重共线性）\n",
    "def remove_high_vif(df, mi_series, threshold=10, min_mi=0.01):\n",
    "    \"\"\"\n",
    "    去除高VIF特征，保留高重要性特征\n",
    "    mi_series: 特征互信息序列（用于判断特征重要性）\n",
    "    min_mi: 低于此值的低重要性特征才会被删除\n",
    "    \"\"\"\n",
    "    while True:\n",
    "        vif = [variance_inflation_factor(df.values, i) for i in range(df.shape[1])]\n",
    "        vif_series = pd.Series(vif, index=df.columns, name='vif')\n",
    "        max_vif = vif_series.max()\n",
    "        if max_vif > threshold:\n",
    "            drop_col = vif_series.idxmax()\n",
    "            # 若特征重要性高（互信息≥min_mi），则提高阈值暂留\n",
    "            if mi_series.get(drop_col, 0) >= min_mi:\n",
    "                print(f\"高重要性特征{drop_col}（VIF={max_vif:.2f}）暂留，提高阈值至15\")\n",
    "                threshold = 15\n",
    "                continue\n",
    "            df = df.drop(columns=drop_col)\n",
    "            print(f\"删除低重要性高VIF特征: {drop_col}, VIF={max_vif:.2f}\")\n",
    "        else:\n",
    "            break\n",
    "    return df\n",
    "\n",
    "# 去除高共线性特征（传入互信息序列用于判断重要性）\n",
    "X_train_vif = remove_high_vif(X_train_selected, mi_series)\n",
    "X_test_vif = X_test_selected[X_train_vif.columns]  # 测试集保持一致\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 3. 特征转换（增强特征表达）\n",
    "# --------------------------\n",
    "# 3.1 创建业务相关衍生特征（结合制造业特点）\n",
    "def create_derived_features(df):\n",
    "    df_new = df.copy()\n",
    "    # 1. 盈利能力：净利润/营业收入（销售净利率）\n",
    "    if 'N_INCOME' in df_new.columns and 'REVENUE' in df_new.columns:\n",
    "        df_new['NET_PROFIT_RATIO'] = df_new['N_INCOME'] / (df_new['REVENUE'] + 1e-6)\n",
    "    \n",
    "    # 2. 资产效率：营业收入/固定资产（固定资产周转率）\n",
    "    if 'REVENUE' in df_new.columns and 'FIXED_ASSETS' in df_new.columns:\n",
    "        df_new['FIXED_ASSET_TURNOVER'] = df_new['REVENUE'] / (df_new['FIXED_ASSETS'] + 1e-6)\n",
    "    \n",
    "    # 3. 偿债能力：货币资金/短期借款（现金比率）\n",
    "    if 'CASH_C_EQUIV' in df_new.columns and 'ST_BORR' in df_new.columns:\n",
    "        df_new['CASH_DEBT_RATIO'] = df_new['CASH_C_EQUIV'] / (df_new['ST_BORR'] + 1e-6)\n",
    "    \n",
    "    # 4. 运营能力：存货/营业成本（存货周转天数相关）\n",
    "    if 'INVENTORIES' in df_new.columns and 'COGS' in df_new.columns:\n",
    "        df_new['INVENTORY_COST_RATIO'] = df_new['INVENTORIES'] / (df_new['COGS'] + 1e-6)\n",
    "    return df_new\n",
    "\n",
    "# 为训练集和测试集创建衍生特征\n",
    "X_train_derived = create_derived_features(X_train_vif)\n",
    "X_test_derived = create_derived_features(X_test_vif)\n",
    "\n",
    "# 优化衍生特征缺失值处理（用训练集衍生特征的中位数填充，而非0）\n",
    "derived_cols = [col for col in X_train_derived.columns if col not in X_train_vif.columns]\n",
    "for col in derived_cols:\n",
    "    # 训练集衍生特征的中位数（用于填充自身和测试集）\n",
    "    train_median = X_train_derived[col].median()\n",
    "    X_train_derived[col] = X_train_derived[col].replace([np.inf, -np.inf], np.nan).fillna(train_median)\n",
    "    X_test_derived[col] = X_test_derived[col].replace([np.inf, -np.inf], np.nan).fillna(train_median)\n",
    "\n",
    "X_train_final = X_train_derived\n",
    "X_test_final = X_test_derived\n",
    "print(f\"最终特征数: {X_train_final.shape[1]}\")\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 4. 特征降维（可选，若特征仍较多）\n",
    "# --------------------------\n",
    "# 用PCA降维（保留95%的方差）\n",
    "#pca = PCA(n_components=0.95, random_state=42)  # 保留95%方差\n",
    "#X_train_pca = pca.fit_transform(X_train_derived)\n",
    "#X_test_pca = pca.transform(X_test_derived)\n",
    "X_train_final = X_train_derived\n",
    "X_test_final = X_test_derived\n",
    "\n",
    "print(f\"最终特征数: {X_train_final.shape[1]}\")  # 输出特征数\n",
    "\n",
    "#print(f\"PCA降维后特征数: {X_train_pca.shape[1]}\")  # 输出降维后的维度\n",
    "print(\"特征工程后训练集形状:\", X_train_final.shape)\n",
    "print(\"特征工程后测试集形状:\", X_test_final.shape)\n",
    "\n",
    "# 保存特征名称，供后续使用\n",
    "feature_names = X_train_final.columns.tolist()\n",
    "print(f\"特征名称: {feature_names[:10]}...\")  # 显示前10个特征名称\n",
    "\n",
    "# --------------------------\n",
    "# 输出最终特征工程后的数据集\n",
    "# --------------------------\n",
    "#print(\"特征工程后训练集形状:\", X_train_pca.shape)\n",
    "#print(\"特征工程后测试集形状:\", X_test_pca.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2e9cd0a-240a-408f-b162-3b4c0c5925b6",
   "metadata": {},
   "source": [
    "### 不均衡学习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "2e718237-008c-48aa-828a-9bc6c037e771",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集类别分布：\n",
      "FLAG\n",
      "0.0    0.989005\n",
      "1.0    0.010995\n",
      "Name: proportion, dtype: float64\n",
      "\n",
      "测试集类别分布：\n",
      "FLAG\n",
      "0.0    0.989113\n",
      "1.0    0.010887\n",
      "Name: proportion, dtype: float64\n",
      "\n",
      "采样后训练集类别分布：\n",
      "FLAG\n",
      "0.0    0.5\n",
      "1.0    0.5\n",
      "Name: proportion, dtype: float64\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\mwj\\anaconda3\\lib\\site-packages\\imblearn\\ensemble\\_forest.py:589: FutureWarning: The default of `replacement` will change from `False` to `True` in version 0.13. This change will follow the implementation proposed in the original paper. Set to `True` to silence this warning and adopt the future behaviour.\n",
      "  warn(\n",
      "C:\\Users\\mwj\\anaconda3\\lib\\site-packages\\imblearn\\ensemble\\_forest.py:601: FutureWarning: The default of `bootstrap` will change from `True` to `False` in version 0.13. This change will follow the implementation proposed in the original paper. Set to `False` to silence this warning and adopt the future behaviour.\n",
      "  warn(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "随机森林(权重) 评估结果：\n",
      "混淆矩阵：\n",
      "[[1812    5]\n",
      " [  20    0]]\n",
      "\n",
      "分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "         0.0       0.99      1.00      0.99      1817\n",
      "         1.0       0.00      0.00      0.00        20\n",
      "\n",
      "    accuracy                           0.99      1837\n",
      "   macro avg       0.49      0.50      0.50      1837\n",
      "weighted avg       0.98      0.99      0.98      1837\n",
      "\n",
      "AUC值：0.4813\n",
      "\n",
      "XGBoost 评估结果：\n",
      "混淆矩阵：\n",
      "[[1813    4]\n",
      " [  20    0]]\n",
      "\n",
      "分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "         0.0       0.99      1.00      0.99      1817\n",
      "         1.0       0.00      0.00      0.00        20\n",
      "\n",
      "    accuracy                           0.99      1837\n",
      "   macro avg       0.49      0.50      0.50      1837\n",
      "weighted avg       0.98      0.99      0.98      1837\n",
      "\n",
      "AUC值：0.5543\n",
      "\n",
      "平衡随机森林 评估结果：\n",
      "混淆矩阵：\n",
      "[[1810    7]\n",
      " [  20    0]]\n",
      "\n",
      "分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "         0.0       0.99      1.00      0.99      1817\n",
      "         1.0       0.00      0.00      0.00        20\n",
      "\n",
      "    accuracy                           0.99      1837\n",
      "   macro avg       0.49      0.50      0.50      1837\n",
      "weighted avg       0.98      0.99      0.98      1837\n",
      "\n",
      "AUC值：0.4563\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAHpCAYAAABTH4/7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACZm0lEQVR4nOzdeZxN9R/H8fe9d+6dhdns+5alRNmVNZEUWhhrIUVRUShSSFH6SVFRk0TW7EpEki2yZBct9m0wljH7eu/5/YGbiWGGmTmzvJ6Pxzzmnu/Z3vfOGN/7ud/zPRbDMAwBAAAAAAAAmchqdgAAAAAAAADkPhSlAAAAAAAAkOkoSgEAAAAAACDTUZQCAAAAAABApqMoBQAAAAAAgExHUQoAAAAAAACZjqIUAAAAAAAAMh1FKQAAAAAAAGQ6ilIAAAAAAADIdBSlACCLGT9+vM6ePStJ2rx5s1auXJmq/fbv36/4+PhkbYcOHdLcuXPlcrnSPScAAIBZNm3apOXLl7uXP//8c124cCFV+/7xxx/XtM2YMUPHjh1Lt3wAUoeiFABkMZ9//rnOnDkjSTp27JjGjx+vxMTEm+43dOhQvfrqq8naIiIiNGzYMJ07d05Dhw7V2rVrMyIyAABApjp27JgmT54swzAkSRMmTNDBgwdvut9ff/2lOnXq6MiRI8na58+fr5UrV+qHH37Q2LFjFRYWlhGxAfwHRSkAyRw4cEANGjRQ5cqVdejQIUnS119/rZIlS2rJkiWSpDNnzqhnz556++231bRpU9ntdiUkJEiSvvnmG3l6eqp3796aMGGC3nrrLXXs2FEnT5407Tndih07dui+++5T9erV9cUXX2j06NEKCgrS6tWrk223c+dOde/eXSNGjFDv3r21e/fuZOtdLpfGjBmjQYMG6Y033lClSpVUpkyZG57bw8ND5cuXlyTZbDbdeeedstvtN9wnLi5OGzdu1IgRI5K1e3p6qlChQipUqJCefvpptWnTRnv37k3lqwAAAG4VfapL1q5dq0qVKqlevXoKDg7WuHHj1KdPH/3666+SpNDQUHXt2lUBAQH69NNP9fHHH6tr166aMGHCDY/r4eGhcuXKyWKxSLrUZ6patepN83z33Xfq16/fNf0xT09PlSlTRq1bt9bBgwf1/PPP39oTBpA2BgD8x65du4xKlSq5l7dt22bMnj3bMAzDSEhIMBo1amTs37/fvb59+/bGzp073culS5c29uzZ417+7LPPjKCgoExIfq2DBw8aZ8+evaV93377bWPAgAHu5aNHjxoFCxY0zp8/bxiGYZw5c8a4++673ctRUVFGnTp1jAsXLrj3ee2114wRI0a4l6Ojo4177rkn2Ta7d+82jhw54l6+9957jdjYWMMwDGPRokXGoEGD3OvmzJljhIeHX5N10qRJxttvv20YhmF88cUXxo4dO9zP/7777nNvt3z5cmPlypVpfi0AAEDa0ae6pFu3bsaoUaPcy4mJiUaNGjWMv//+2zAMw1i9erVRs2ZN9/qkpCSjatWqxs8//5zsOEuXLjWcTqdhGNf2ka7uP4WGhhrffffdNTmcTqdRrVo1IywszAgLCzOGDBliJCYmGoZhGF26dDGWL1/uPv+4ceOM06dP39LzBZB6jJQCcI177rlHhQsXdo8KWrRokdq2bStJmjZtmmrXru0eySNJw4cP1759+1I8XpMmTfT7779nbOgU/Pjjjzp37ly6HKtUqVIqWbKk9uzZI0kaO3asHn30UeXLl0+SlCdPHjVo0EBTp06VdGl4+FdffaXXXnvNfQwfHx+NGjVKO3bscLft3LlT9913nx544AE98MADOnDggJo3b64HHnhAQ4YM0axZs9zrevbsqfHjxyfL5XK5NG/ePL322mtyuVz64IMPdPLkScXExCgsLEwXL17UL7/8olmzZunXX39V165d9cMPP6TLawIAAFJGn+r6PDw81KJFC3377bfXXW+z2fTQQw9pw4YNydr79Omj+vXrX7ePdHX/qVmzZurRo4ciIiKS7b9gwQL3qKz58+dr3bp1SkhI0Pnz55WQkKBdu3Zp8eLFmjx5subNm6eePXsqNjY2XZ4zgOvzMDsAgKypT58+mjBhgu6++24VKFBAHh6X/lxMmzZN77zzTrJt77rrLt11110pHuvChQsKCAjIyLjX9ccff+jjjz9Ws2bN0u2YkZGRyp8/vyRp4cKF+vDDD5Otr1+/vt5//329+uqrmjZtmh566CF5eXkl26ZFixbJ5oiyWq1q2bKlJk2aJEmqVq2aVqxYIS8vL3333XfatGmTPvjgA0lSgwYN1KZNm2TH++qrr/Tss88qb968WrBggdq2bavp06dr+PDhyps3r6Kjo5WQkKAaNWqoTZs2GjlyZLq9HgAA4MboU11fQECAjh8/nuL6yMhIlS1bNlmb1WrVvHnzVKJEiWv6SFf3n1auXKlJkybJz8/PvW90dLS+/fZbzZ07V5I0depUDRo0SHfffbdKlSqlo0ePqmTJkmrYsKHq1Kmjnj17pttzBZAyilIAruuJJ57Q66+/rlGjRmnYsGGSJKfTqS1btujOO+9M9XESExP18ccfa/Dgwcnav/rqK/3zzz8qXLiwDhw4oIEDB6pcuXLu9X/++afGjRunihUr6uTJk6pZs6aeeuop93rDMDRu3Dh5enoqLCxMEydOVLNmzfT1119LkpYtW6ajR4/Kw8NDc+bMUeHChVW0aFE9/vjjt/yaLFy4UCVLllSVKlUUFxen/fv3q3Tp0sm2KV68uHvOpo0bN6px48bXHMdqtcrT09O9bLPZbjnTzp079e6776pnz56aNGmSZs+ercWLF8vb21sWi0Xh4eGqU6eOHnnkEfc+4eHhyps3722dFwAApA59quvbv3+/atSocd11Bw8e1Nq1azVq1Khk7bfTd+nevbsk6ZNPPtHJkyf19NNPq1WrVmrZsqUsFov69eunGjVq6P7773fvc+HCBfeIeAAZg6IUgOvy8PBQz549tX37dgUGBkqSzp07p7i4uFT95zx37lytXbtW33//vR588EF16NDBve7777/X77//rokTJ0q6NMln69attWHDBtntdkVHR6tLly76+eef3ed+6qmnVLx4cT3wwAOSpDlz5qhkyZIKCgqSJD333HN6/fXX3ee4UoSZPXu2OnTokKZO39V27dql4OBgTZ8+XQ899JD7srcLFy7IMAzlzZs32fYBAQGKi4vThQsXdOrUKRUqVOim57idDlbVqlX1yiuvqHfv3po4caLefPNN+fj4uNf7+vrq4sWLio+P16lTpxQSEqKBAweqUqVKmjhxIoUpAAAyGH2qa23btk2///67PvnkE3fb2bNnFRwcrLVr18put2vFihXu0elX3G5RKm/evKpatar69Omjjz76SJLcE6X7+voqLCxM58+fV0hIiNavX693331XP/zwg2rVqnXL5wVwYxSlAKSoUKFCWrt2rWJiYuTj4+P+T9u4fOtdSZo8ebK+//57+fv7a+DAgapSpYokqX379qpSpYpeeukljRo1Ss8884y++eYbSdLQoUO1cOFC9zEKFy6sWrVqadasWerWrZu+/PJLtWjR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okVq2bKldu/e7W4LCAhQnTp13MuVKlXSkSNHbpoDAJAxDMNQUPBGbTt67ad3abXv3Yfl48gS3ZpMkxX7T7di1uZjOnwuSo/dW1xVS/hn6LkAALlPdnpvf7WqVatq06ZNydry58+f7P9om82mChUqaM2aNTp37pxee+01ffPNN9q5c6fmz5+vkSNHas2aNVq+fHmy4wQHB+utt97SkSNHVLhwYY0bN06vvPKK9u/fr2HDhumOO+5QhQoV1Lhx41vKnhbZqve2adMm1axZ071cvnx5/fPPP4qPj5enp+c1248aNUrvvPPOLZ0rNtGpysN+uuWstyOtHevExETVr19fS5cuVadOnW647bJly9SoUaNkbX5+fqpevfoN95s+fbr27t17TfvTTz+tKlWqpLhfWn9mVwsJCdGrr76q6OhoxcXFKTg4WHny5LnpuvDwcIWGhurhhx9OdrzChQvrjTfekHTpNVu9erU++OCDG2YAAGSc2ETnbRWkYo/uklfJKrJYbemYKufJzP7TrVj2xyn9uv+cKhfzoygFAEh32em9/dUiIyMVEBCQrG3jxo26//773cup/YDq6kEds2fP1t9//63Fixdr3bp1Gjt2rKRLBa/27dvrjTfeUKFChSRJsbGxqT7HrcpWRalTp04pf/78ydp8fX11/vx5FStW7JrtBw8erP79+7uXIyIiVLJkyQzPmZliYmLk5eWl++67T2+99dZNtz969KgqVqyY5vN06dLlVuKl+Wd2tT///FPffvutrFarxo8fr48++kjDhg276bqpU6fqhRde0KlTp6573F9++UXvvvuunn/+eZUpU+aWnhcAIH1tHdJMPo7UFZcSExP11uA3NGH2pxo0+E0NG/6OvO0UplJC/wkAgOzl7Nmz+ueff9SgQQNJlwpJP/30k8qVK6evv/7avd2tFIxq1aqlAgUKKG/evLLb7SpQoIB7na+vr3bt2iVPT0+dOXNGn3/+uaZMmZKh75uzVVEqISHhmok5XS6XvLy8rru9p6fnTUfjpMTbbtO+dx+++YYZIC0d699++0116tRRYGCgEhISFBERIT8/vxS3j4mJueXhg7cirT+zq82ZM8f9uG3btmrbtq278JTSuq1bt6patWo3/Lk3bdpUTZo0Ua9eveTr66vHHnssrU8LAJDOfBy2VH2SePLkSbVv316//fabJMliuG5r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FRWlxo0ba8eOHWrUqJHZ0QCkk5MnT+quu+7SmDFjJEn333+/unXrJkmKjY01MxoAAIApuHwPAEx24sQJdejQQb/99pskadCgQRo5cqQ8PPgTDeQU33zzjUqUKKHu3bu72x5++GEFBARIknx9fU1KBgAAYB7e8QCAybZv367ffvtN/v7+mjp1qh5//HGzIwFIZ6VKlbpue926dSVJK1asyMw4AAAAWUKaLt9LSkpKcV1ISIjef//92w4EALnNY489ps8++0zbtm2jIAXkMDExMWZHAAAAyLJSPVLq2LFjatWqlXbv3q39+/erQoUKkqTp06fL5XKpSJEiWrlypd58880MCwsAOcGFCxf06quv6r333lPJkiUlSS+//LLJqQBkhJEjR2rr1q3y8vKSYRg33DYpKUm+vr766quv5O/vn0kJAQAAzJPqolSJEiUkScePH1fXrl21dOlSORwOzZ07V3PnzpW3t7c++OCDDAsKADnB1q1b1a5dOx05ckQnTpzQqlWrzI4EIAOldRR5gwYNtG/fPt1///0ZlAgAACDrSHVRymq1qkCBAipZsqSWL1+uyZMnq3bt2po0aZIcDockyWKxZFhQAMjODMPQxIkT1bdvXyUkJOiOO+7Qxx9/bHYsAJlkxYoVOnLkyHVvYOB0OlWuXDk1bdpUL7zwAgUpAACQa6R5onPDMPT+++9r6NChyps3r3r16iU/Pz+NHj36psPSAeAKwzAUm+g0O0amiI6O1it9XtK3M2dKklo/9riCv5qkgIAAxSSkPFdfZohJyB0/A8BsefLkkZ+fn2w2mywWiywWiwzDUFJSkgzDcM899fTTT5ucFAAAIPOkuigVHR0t6dJoqHLlyumll17SmDFjVL16dT322GPudQBwM4ZhKCh4o7YdDTM7SoZLijir0HnDlXjuqGSxKqBxN+26s43qfbzZ7GgAMkl8fLzy5cun+vXrS5K++uor9ezZUzt27NDo0aP17bffurelLwUAAHKTVN19b9euXbr//vt17NgxDRw4UC+88ILKlSunSZMmqX79+urZs6datWqlPXv2qFWrVmrevLm6du2a0dkBZFOxic5cUZCSJKu3r2SxyJYnUIU7vif/um2z5JvOWqUD5W23mR0DyJE2bNigLl26aPPmzTp06JAGDRokSfL399fx48dNTgcAAGCeVI+U2rFjh5o1a6bq1avr7bfflpeXl9q3b6+vv/5azZs3V9++fdWkSRMtWbIkI/MCyGG2DmkmH0fOKoYkJibKZrPJar1U9z/cs6q8vL1VtGhRk5OlzNtuy5LFMiAnePDBB7Vp0yZNmzZN5cuXV7Vq1SRJ5cqVc8/LCQAAkBulqih17733Sro0pLxTp07au3evunXrpv79+6tGjRpasWJFhoYEkHP5OGzycaR5erss6+TJk+rQoYMeeeQRvfXWW5Kku++saHIqAGb56quvdPToUTkcDhmGoePHj+vIkSP66KOPlC9fPl28eFGRkZHy9fU1OyoAAECmu6V3gv7+/nrvvfd09OhRFS9eXBMnTpTEPAgAcrdVq1apU6dOCg0N1b59+/Tiiy8qMDDQ7FgATFS/fn3de++97gnODcPQDz/8oLp16+rs2bOKjIxUz549VaJECY0cOVJeXl5mRwYAAMg0qZpT6mpxcXF69dVX5XK5VLFiRS1btkxHjx7NiGwAkC24XC69//77euihhxQaGqp7771Xv//+OwUpAKpcubLq1KmjsLAw9ejRQ5Lk5+en+++/X08++aRKliyp2bNnq3v37urbt6/7xjIAAAC5QZqKUnfddZeioqI0ZswYPfLII5KkHj16qHfv3pKk0NDQ9E8IAFlYWFiYHn/8cb311ltyuVzq3r27Nm7cqDvuuMPsaACyiP3792vDhg1auHChatasKR8fH4WFXbrZQ1RUlCTp7rvv1kcffaRx48aZmBQAACBzpenyvQkTJkiSChQo4G4rVaqUfvjhB0lSnz590jEaAGRtiYmJqlevnv766y95enpqwoQJeu6558yOBSCLqVChgt5++2338oIFC+Tp6SlJqlu3rrvd19dXzz33nDZv3pysHQAAIKdK8+V712OzXbpz1gsvvJAehwOAbMFut+vVV19VuXLltHHjRgpSAFLlSkFKkj777LNk64oUKUJBCgAA5BqpLkodO3ZMLpcrVV8AkFPFxMRo//797uXnn39eu3fvVvXq1U1MBSC7udJfWr16dbL2n3/+2Yw4AAAApkjV5XshISHq0aOHHA6HrFarDMO47nZX7iqzb98+DRs2TN26dUvXsABgpv379ysoKEgRERHavn27AgMDZbFYlCdPHrOjAcjCjhw5ojJlyriXt2/frk8++URvv/22unXrpmPHjkmSoqOj9dxzz2nr1q0qVKiQSWkBAAAyT6qKUsWKFdOKFSskXZrMPF++fPLw+HdXwzAUHx8vu90um82mzz//XPfcc0/GJAYAEyxcuFDPPPOMIiMjVbhwYR05coS76wG4qejoaNWrV081a9aU0+nUli1b9Msvv+j48eMqV65cspsi9OnTR0OGDKEgBQAAco00TXQuScuWLdO3334rh8PhbjMMQ4mJierQoYO6d++u2rVrcykLgBwhMTFRb7zxhj7++GNJUsOGDTVnzhwVLVrU5GQAsoM8efKoUqVK7pvCNGnSRPfee697vcViUUhIiMaMGaO2bduqZcuWZkUFAADIdKkuSp0/f15PPfWUli9frjZt2mjmzJl69tlnkxWnrqhdu3a6hgQAM4SEhKhDhw5av369JOm1117T+++/L7vdbnIyANmJxWK55vGJEyc0depUnT59Wtu3b9eHH37ovnEMAABAbpHqic4DAgJ08uRJSZcmPR85cqSefPJJPfHEE5oyZYri4uIyLCQAmOGNN97Q+vXr5efnp4ULF+rDDz+kIAUgXTgcDjmdTiUkJGjatGnq06ePoqOjzY4FAACQqVJdlLLZbCpQoIDWr1+vypUrq0KFClq6dKlmzpypc+fOqXXr1vr2228zMisAZKqxY8eqdevW2rp1q5588kmz4wDIphITE7Vu3TqtXbtWFy9eVGxsrAoWLKhnn31WJUuW1Ny5c9W7d289++yzioiIMDsuAABApkl1UeqK77//Xh07dtSRI0f066+/avr06QoJCdGCBQsUERGhd999NyNyAkCGCwsL04QJE9x3GM2fP78WL16sChUqmJwMQHaVkJCgRx99VJs2bdKWLVvUtm1bhYSEuItPLpdLklS1alWNHz9eo0ePNjMuAABApkr1nFIul0seHh7u+VQuXLig2bNna+/evRoyZIj8/Pz0wgsvaP/+/Zo+fbq6dOmSkbkBXMUwDMUmOs2OkWoxCVkv6/bt2xUUFKTDhw/L09NTPXr0MDsSgBzA4XBo8ODB17QvWbJE0qW/38uXL1fhwoVVvXp1Pf7449q/fz/FcAAAkCukqii1a9cu9e/fX926dZOHh4cuXryofPny6cUXX5RhGFqxYoX7TlQVKlRQYmKizp8/r/z582doeACX3tAEBW/UtqNhZkfJlgzD0Ndff62XX35Z8fHxKlu2rGrUqGF2LAA5zLZt21SpUiXlzZtXx44dU6lSpSRJq1atkoeHh0aOHKnffvtNL730ks6cOWNyWgAAgMyRqqJUuXLlNHDgQK1evVrNmzfX+fPnZbFYVLx4cUmX3tRNmDBBkuR0OhUTE6N27drpxRdfzLjkACRJsYnObFuQqlU6UN528+42FRMTo5deeknffPONJKl169aaOnWqAgMDTcsEIOeZNm2aZs2apXfeeUd169bVpEmTtGPHjmR35TMMQydOnFD9+vVVrVo188ICAABkolQVpXx9ffXwww/r4YcfliTt3LlTM2bMUPHixdW3b19uYQxkEVuHNJOPI/v8e/S225K9KctM+/fvV1BQkHbv3i2r1ar33ntPAwcOlNWa5qn2AOCG7rvvPnXt2tW9XLt2bb3zzjum/f0DAADIKlI9p9TVqlWrpmrVqik0NFTLli1Tq1at0jsXgFvg47DJx3FL/6xzncOHD2vPnj0qVKiQZs+erSZNmpgdCUAOVbFixWTLhQoVUnx8vLy8vExKBAAAkDXc1rvXQoUKUZACkC01b95c33zzjZo1a6ZixYqZHQdALlK3bl2zIwAAAGQJqb5O5fz58xozZsxNtzt27Nh17zIDAGYKCQlR69atdfDgQXdb165dKUgBAAAAgElSXZQKDg7Wjz/+6F4OCwvTo48+es12U6ZM0ebNm9MnHQCkgzVr1qhGjRpasmSJnn32WbPjAMhlwsPDU71tWFj2vHEFAADArUh1Ueqtt96SYRjuZYvFoj179lyz3dtvv50+yQDgNrlcLn3wwQdq2rSpzpw5o6pVq2rSpElmxwKQy3h7e2vatGk33S44OFh58uTJhEQAAABZQ5puM3X1XWICAgJUvnz5dA8EAOkhLCxMTz75pAYPHiyXy6WuXbtq06ZNqlChgtnRAOQyDodDjRs31uDBgxUREXHN+sTERA0fPlxNmjSRw+EwISEAAIA5bmuic25lDCArOnz4sJo1a6ZDhw7J09NTn332mXr06MHfLACmKV26tPr166c+ffrIZrPpjjvukKenp44cOaKzZ8/qvffe48M+AACQ66SpKPXnn3+652MxDCPZ8hVXX+IHAGYoVqyYChQoIJfLpfnz56tmzZpmRwIAFSpUSFOnTtXRo0e1c+dOJSQk6LHHHlPFihXNjgYAAGCKNBWlypQpk2zOqOHDh1+zjWEYTCQMINPFxsbKbrfLw8NDnp6eWrhwoby9vZUvXz6zowFAMqVLl1bp0qXNjgEAAGC6NBWlvL296UQByHIOHDigoKAgPfLIIxo1apQkqXjx4ianAgAAAADcyC3PKbVu3TqVKVPmmnYu3wOQmb777jt169ZNEREROn36tAYOHKjAwECzYwEAAAAAbiJNRSmn0ylJiouL08SJE5k0GIBpkpKSNHjwYI0ZM0aSVL9+fc2ZM4eCFAAAAABkE2kqSl25jbGXl5dmzJiR4naNGjW6vVQAcAOnTp1Sx44dtW7dOklS//799cEHH8hut5ucDAAAAACQWmkqSq1ZsyZV2507d+5WsgDATSUmJqphw4Y6ePCgfH19NXnyZAUFBZkdCwAAAACQRta0bOzv73/D9WFhYZLkHr0AAOnNbrfrnXfeUZUqVbR161YKUgAAAACQTaWpKLV+/foU182bN0+dO3eWJBUoUOD2UgG5iGEYiklIuo0vp9lPIcNdvHhRu3fvdi8/9dRT2rZtmypWrGhiKgAAAADA7Uj15XvffvutRo0aleyN4dUaN26sL774QqdPn1a/fv307bffpltIIKcyDENBwRu17WiY2VGyrB07digoKEhxcXHavn27ChcuLElyOBwmJwOA1Lt48aICAgKuu2737t36+uuv9cknn2RuKAAAAJOleqRUp06dlC9fPsXFxem3337TsWPHkn3FxcXJMAxNnjxZL7zwQkZmBnKM2ERnuhWkapUOlLfdli7Hyiq+/vpr3X///Tp06JDsdrvOnj1rdiQASLMjR46ocuXKkqTffvvN3f7BBx/oyy+/VGJiovbs2WNWPAAAANOkaaJzq9WqI0eOaPjw4dqzZ4+aNWum06dP648//tBDDz0ki8Uim82mBx54IIPiAjnX1iHN5OO49aKSt90mi8WSjonMExsbq5dfflmTJ0+WJD366KOaPn268uXLZ3IyAEi7MmXKqESJEjp16pSGDh2qyZMny9fXV/v27dPUqVNzzN9uAACAtErTnFKSdOedd+rNN9/UnXfeqW+++UaDBg3SXXfdpWnTpunkyZPueaUApI2PwyYfh8ctf+WUNzUHDx5UvXr1NHnyZFmtVo0cOVI//PADBSkA2VrevHlVtGhR/fTTT1q6dKn++ecfjR071n2TGAAAgNwoVSOl/vjjD508eVJxcXFKSEiQxWJJ9nVFsWLFVLJkyQwLCyDne++997Rz504VLFhQ3377rZo2bWp2JABIF0lJSXr66acVHBysgIAAtWnTRrVq1dKbb74pwzDMjgcAAJDpUjVS6tixY1q6dKkOHz6s9u3bKzo6+rrb5ZSRGgDMM27cOHXp0kU7duygIAUgR9i/f78kycPDQ927d1e3bt0UEhKiLl266IknnpD0bx/KMAwlJiaaFRUAACBTpaoo9eijj+rTTz/VnXfeqe+++0558uS57nbbtm3T//73v3QNCCBnO3XqlEaOHOkeJeDn56dp06apePHiJicDgNu3bt069e7dW0ePHlWnTp3UpEkTNWzYUMHBwSpfvryGDBmi1q1ba8+ePWrVqpUeffRRvfjii2bHBgAAyBRpmujcYrHIMAwtXLhQx48f16effqr9+/e7H1etWlW7d+9WQkICt2sHcFNr165Vx44ddfr0afn5+alv375mRwKAdFWqVCmtXLlSTZo0UZ8+ffTmm28qT5486tu3r0aOHKm6detq0KBBatKkiZYsWWJ2XAAAgEyVponODcNQVFSU7rvvPo0aNUqlSpXSQw89pA8//FClS5eWw+HQ//73Py1YsCCj8gLIAQzD0IcffqimTZvq9OnTuvvuu/Xwww+bHQsA0l2ZMmUkXfpgr169eurfv782bNggDw8PtWrVShcvXjQ1HwAAgJnSNFLq3Llz8vX1VadOna67fuzYsSpRooQCAgLSIxuAHCg8PFzPPPOMvvvuO0lyT/qb0mXBAJCTnD9/Xl26dFFkZKRcLpdGjRoliXk5AQBA7pSmkVKTJ0++4fqIiAhJUoMGDW49EYAca9euXapZs6a+++47ORwOffHFF5o2bRoFKQC5QnR0tEaMGKEaNWqoZMmS2rJli3bs2GF2LAAAANOkqShVu3btG67/+eefJUm+vr63nghAjhUZGakjR46odOnSWr9+vXr16sXoAAC5QpMmTZSQkKAvv/xSVatWlST16tXLfYOYkJAQM+MBAACYIk2X791M/vz50/NwAHIAwzDchacGDRpo/vz5atiwIX8vAOQqQ4cOvaatQIECmj17tiTp448/zuxIAAAApkvTSCkASIuDBw+qUaNG2rt3r7vtiSeeoCAFAP/x6KOPmh0BAAAg06XrSCkAuOL7779Xt27dFB4ert69e2vdunVmRwIA073//vu68847U1wfGBioJk2aZGIiAAAA85g6UiosLEzPPPOM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",
      "text/plain": [
       "<Figure size 1200x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "最终使用的特征数量: 69\n",
      "前10个特征: ['TICKER_SYMBOL', 'END_DATE_REP', 'AR', 'INT_RECEIV', 'INVENTORIES', 'AVAIL_FOR_SALE_FA', 'LT_EQUITY_INVEST', 'FIXED_ASSETS', 'CIP', 'INTAN_ASSETS']\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.metrics import (confusion_matrix, classification_report,\n",
    "                             roc_auc_score, roc_curve, precision_recall_curve)\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "import xgboost as xgb\n",
    "from imblearn.over_sampling import SMOTE\n",
    "from imblearn.combine import SMOTETomek\n",
    "from imblearn.ensemble import BalancedRandomForestClassifier\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 全局设置字体，替换成你想选的字体名，比如 'Microsoft YaHei'\n",
    "plt.rcParams['font.family'] = 'SimSun'\n",
    "\n",
    "# 解决负号显示为方块的问题（可选但推荐）\n",
    "plt.rcParams['axes.unicode_minus'] = False\n",
    "\n",
    "# --------------------------\n",
    "# 1. 数据衔接（使用特征工程输出的数据集）\n",
    "# --------------------------\n",
    "# 使用特征工程后的数据（X_train_final, X_test_final）\n",
    "print(\"训练集类别分布：\")\n",
    "print(pd.Series(y_train).value_counts(normalize=True))\n",
    "print(\"\\n测试集类别分布：\")\n",
    "print(pd.Series(y_test).value_counts(normalize=True))\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 2. 采样处理不均衡数据\n",
    "# --------------------------\n",
    "# 采用SMOTETomek混合采样（过采样+去噪）\n",
    "sampler = SMOTETomek(random_state=42)\n",
    "X_train_resampled, y_train_resampled = sampler.fit_resample(X_train_final, y_train)\n",
    "\n",
    "# 采样后分布\n",
    "print(\"\\n采样后训练集类别分布：\")\n",
    "print(pd.Series(y_train_resampled).value_counts(normalize=True))\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 3. 训练适配不均衡的模型\n",
    "# --------------------------\n",
    "def train_balanced_models(X_train, y_train, X_test, y_test):\n",
    "    # 模型1：带类别权重的随机森林\n",
    "    rf = RandomForestClassifier(\n",
    "        n_estimators=100,\n",
    "        class_weight='balanced',  # 自动平衡类别权重\n",
    "        random_state=42,\n",
    "        n_jobs=-1\n",
    "    )\n",
    "    rf.fit(X_train, y_train)\n",
    "    \n",
    "    # 模型2：XGBoost（手动设置正负样本权重）\n",
    "    scale_pos_weight = (y_train == 0).sum() / (y_train == 1).sum()  # 负样本/正样本\n",
    "    xgb_clf = xgb.XGBClassifier(\n",
    "        n_estimators=100,\n",
    "        scale_pos_weight=scale_pos_weight,  # 关键参数：平衡权重\n",
    "        learning_rate=0.1,\n",
    "        random_state=42\n",
    "    )\n",
    "    xgb_clf.fit(X_train, y_train)\n",
    "    \n",
    "    # 模型3：平衡随机森林（自带采样机制）\n",
    "    brf = BalancedRandomForestClassifier(\n",
    "        n_estimators=100,\n",
    "        sampling_strategy='auto',  # 每个树都平衡采样\n",
    "        random_state=42,\n",
    "        n_jobs=-1\n",
    "    )\n",
    "    brf.fit(X_train, y_train)\n",
    "    \n",
    "    # 评估并返回模型\n",
    "    models = {'随机森林(权重)': rf, 'XGBoost': xgb_clf, '平衡随机森林': brf}\n",
    "    for name, model in models.items():\n",
    "        print(f\"\\n{name} 评估结果：\")\n",
    "        y_pred = model.predict(X_test)\n",
    "        y_prob = model.predict_proba(X_test)[:, 1]\n",
    "        print(\"混淆矩阵：\")\n",
    "        print(confusion_matrix(y_test, y_pred))\n",
    "        print(\"\\n分类报告：\")\n",
    "        print(classification_report(y_test, y_pred))\n",
    "        print(f\"AUC值：{roc_auc_score(y_test, y_prob):.4f}\")\n",
    "    return models\n",
    "\n",
    "# 用采样后的训练集训练模型\n",
    "models = train_balanced_models(\n",
    "    X_train_resampled, y_train_resampled,\n",
    "    X_test_final, y_test\n",
    ")\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 4. 关键指标可视化\n",
    "# --------------------------\n",
    "def plot_metrics(model, X_test, y_test, model_name):\n",
    "    y_prob = model.predict_proba(X_test)[:, 1]\n",
    "    \n",
    "    # ROC曲线（整体区分能力）\n",
    "    fpr, tpr, _ = roc_curve(y_test, y_prob)\n",
    "    auc = roc_auc_score(y_test, y_prob)\n",
    "    \n",
    "    # PR曲线（聚焦少数类识别）\n",
    "    precision, recall, _ = precision_recall_curve(y_test, y_prob)\n",
    "    \n",
    "    plt.figure(figsize=(12, 5))\n",
    "    # 左：ROC曲线\n",
    "    plt.subplot(1, 2, 1)\n",
    "    plt.plot(fpr, tpr, label=f'AUC = {auc:.4f}')\n",
    "    plt.plot([0, 1], [0, 1], 'k--')\n",
    "    plt.xlabel('假正例率')\n",
    "    plt.ylabel('真正例率')\n",
    "    plt.title(f'{model_name} ROC曲线')\n",
    "    plt.legend()\n",
    "    \n",
    "    # 右：PR曲线\n",
    "    plt.subplot(1, 2, 2)\n",
    "    plt.plot(recall, precision, label='PR曲线')\n",
    "    plt.xlabel('召回率（漏检率）')\n",
    "    plt.ylabel('精确率（误判率）')\n",
    "    plt.title(f'{model_name} PR曲线')\n",
    "    plt.legend()\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "\n",
    "# 可视化效果最优模型（以XGBoost为例）\n",
    "plot_metrics(models['XGBoost'], X_test_final, y_test, 'XGBoost')\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 5. 保存模型和特征信息（为第六年预测做准备）\n",
    "# --------------------------\n",
    "# 选择最优模型（这里以XGBoost为例）\n",
    "best_model = models['XGBoost']\n",
    "\n",
    "# 保存特征名称，用于第六年数据的特征对齐\n",
    "final_feature_names = X_train_final.columns.tolist()\n",
    "print(f\"\\n最终使用的特征数量: {len(final_feature_names)}\")\n",
    "print(f\"前10个特征: {final_feature_names[:10]}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "id": "1fc56809-1d4f-4bb7-877c-f7b70fe1f35f",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true,
     "source_hidden": true
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集类别分布：\n",
      "FLAG\n",
      "0.0    0.989005\n",
      "1.0    0.010995\n",
      "Name: proportion, dtype: float64\n",
      "\n",
      "测试集类别分布：\n",
      "FLAG\n",
      "0.0    0.989113\n",
      "1.0    0.010887\n",
      "Name: proportion, dtype: float64\n",
      "\n",
      "采样后训练集类别分布：\n",
      "FLAG\n",
      "0.0    0.5\n",
      "1.0    0.5\n",
      "Name: proportion, dtype: float64\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\mwj\\anaconda3\\lib\\site-packages\\imblearn\\ensemble\\_forest.py:589: FutureWarning: The default of `replacement` will change from `False` to `True` in version 0.13. This change will follow the implementation proposed in the original paper. Set to `True` to silence this warning and adopt the future behaviour.\n",
      "  warn(\n",
      "C:\\Users\\mwj\\anaconda3\\lib\\site-packages\\imblearn\\ensemble\\_forest.py:601: FutureWarning: The default of `bootstrap` will change from `True` to `False` in version 0.13. This change will follow the implementation proposed in the original paper. Set to `False` to silence this warning and adopt the future behaviour.\n",
      "  warn(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "随机森林(权重) 评估结果：\n",
      "混淆矩阵：\n",
      "[[1748   69]\n",
      " [  18    2]]\n",
      "\n",
      "分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "         0.0       0.99      0.96      0.98      1817\n",
      "         1.0       0.03      0.10      0.04        20\n",
      "\n",
      "    accuracy                           0.95      1837\n",
      "   macro avg       0.51      0.53      0.51      1837\n",
      "weighted avg       0.98      0.95      0.97      1837\n",
      "\n",
      "AUC值：0.5547\n",
      "\n",
      "XGBoost 评估结果：\n",
      "混淆矩阵：\n",
      "[[1662  155]\n",
      " [  17    3]]\n",
      "\n",
      "分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "         0.0       0.99      0.91      0.95      1817\n",
      "         1.0       0.02      0.15      0.03        20\n",
      "\n",
      "    accuracy                           0.91      1837\n",
      "   macro avg       0.50      0.53      0.49      1837\n",
      "weighted avg       0.98      0.91      0.94      1837\n",
      "\n",
      "AUC值：0.6310\n",
      "\n",
      "平衡随机森林 评估结果：\n",
      "混淆矩阵：\n",
      "[[1748   69]\n",
      " [  19    1]]\n",
      "\n",
      "分类报告：\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "         0.0       0.99      0.96      0.98      1817\n",
      "         1.0       0.01      0.05      0.02        20\n",
      "\n",
      "    accuracy                           0.95      1837\n",
      "   macro avg       0.50      0.51      0.50      1837\n",
      "weighted avg       0.98      0.95      0.97      1837\n",
      "\n",
      "AUC值：0.5241\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAHpCAYAAABTH4/7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACZeUlEQVR4nOzdeZxN9R/H8fe9d+6dxTLGvm9ZSoSx70SSaGGshRRFomRLoYT0k0pFSZI9OykRyhLZ90jZt8FgxsyYfe49vz+Gm8k2mJkzy+v5eMxj7jnne8553ztjfO/nfs/3WAzDMAQAAAAAAACkIqvZAQAAAAAAAJD5UJQCAAAAAABAqqMoBQAAAAAAgFRHUQoAAAAAAACpjqIUAAAAAAAAUh1FKQAAAAAAAKQ6ilIAAAAAAABIdRSlAAAAAAAAkOooSgEAAAAAACDVUZQCgDRm/PjxunDhgiRpy5YtWr16dZL2O3TokGJiYhKtO3r0qObNmyeXy5XsOQEAAMyyefNmrVixwr385ZdfKjg4OEn7/vnnnzesmzlzpk6ePJls+QAkDUUpAEhjvvzyS50/f16SdPLkSY0fP15xcXF33G/o0KF64403Eq0LCwvTsGHDdPHiRQ0dOlTr1q1LicgAAACp6uTJk5oyZYoMw5AkTZgwQUeOHLnjfgcPHlT16tV1/PjxROsXLFig1atX68cff9Snn36qkJCQlIgN4D8oSgFI5PDhw6pbt67KlSuno0ePSpK+/fZbFSlSRD/99JMk6fz58+revbveffddNW7cWHa7XbGxsZKkqVOnytPTUz179tSECRP0zjvvqH379jpz5oxpz+le7Nq1SzVr1lTlypX11VdfacyYMQoICNCaNWsStdu9e7e6du2qESNGqGfPntq7d2+i7S6XS2PHjtWgQYP01ltvqWzZsipevPhtz+3h4aFSpUpJkmw2mx588EHZ7fbb7hMdHa1NmzZpxIgRidZ7enoqb968yps3r55//nm1atVK+/fvT+KrAAAA7hV9qgTr1q1T2bJlVbt2bU2cOFHjxo1T79699fvvv0uSgoKC1LlzZ+XIkUOff/65PvnkE3Xu3FkTJky47XE9PDxUsmRJWSwWSQl9pgoVKtwxz5IlS9S3b98b+mOenp4qXry4WrZsqSNHjujll1++tycM4O4YAPAfe/bsMcqWLete3rFjhzFnzhzDMAwjNjbWqF+/vnHo0CH39rZt2xq7d+92LxcrVszYt2+fe/mLL74wAgICUiH5jY4cOWJcuHDhnvZ99913jX79+rmXT5w4YeTJk8e4dOmSYRiGcf78eePhhx92L1+5csWoXr26ERwc7N6nf//+xogRI9zLERERxiOPPJKozd69e43jx4+7lytWrGhERUUZhmEYixcvNgYNGuTeNnfuXCM0NPSGrJMnTzbeffddwzAM46uvvjJ27drlfv41a9Z0t1uxYoWxevXqu34tAADA3aNPlaBLly7G6NGj3ctxcXGGv7+/8ffffxuGYRhr1qwxqlSp4t4eHx9vVKhQwVi1alWi4yxbtsxwOp2GYdzYR7q+/xQUFGQsWbLkhhxOp9OoVKmSERISYoSEhBhDhgwx4uLiDMMwjE6dOhkrVqxwn3/cuHHGuXPn7un5Akg6RkoBuMEjjzyifPnyuUcFLV68WK1bt5YkTZ8+XdWqVXOP5JGk9957TwcOHLjl8Ro1aqRt27albOhb+Pnnn3Xx4sVkOVbRokVVpEgR7du3T5L06aefqnnz5sqZM6ckKUuWLKpbt66mTZsmKWF4+DfffKP+/fu7j+Hj46PRo0dr165d7nW7d+9WzZo11bBhQzVs2FCHDx9W06ZN1bBhQw0ZMkSzZ892b+vevbvGjx+fKJfL5dL8+fPVv39/uVwuffjhhzpz5owiIyMVEhKiy5cv69dff9Xs2bP1+++/q3Pnzvrxxx+T5TUBAAC3Rp/q5jw8PNSsWTN9//33N91us9n02GOPaePGjYnW9+7dW3Xq1LlpH+n6/lOTJk3UrVs3hYWFJdp/4cKF7lFZCxYs0Pr16xUbG6tLly4pNjZWe/bs0dKlSzVlyhTNnz9f3bt3V1RUVLI8ZwA352F2AABpU+/evTVhwgQ9/PDDyp07tzw8Ev5cTJ8+XcOHD0/U9qGHHtJDDz10y2MFBwcrR44cKRn3pv7880998sknatKkSbIdMzw8XLly5ZIkLVq0SB999FGi7XXq1NEHH3ygN954Q9OnT9djjz0mLy+vRG2aNWuWaI4oq9WqJ598UpMnT5YkVapUSStXrpSXl5eWLFmizZs368MPP5Qk1a1bV61atUp0vG+++UYvvviismbNqoULF6p169aaMWOG3nvvPWXNmlURERGKjY2Vv7+/WrVqpZEjRybb6wEAAG6PPtXN5ciRQ6dOnbrl9vDwcJUoUSLROqvVqvnz56tw4cI39JGu7z+tXr1akydPVvbs2d37RkRE6Pvvv9e8efMkSdOmTdOgQYP08MMPq2jRojpx4oSKFCmievXqqXr16urevXuyPVcAt0ZRCsBNPfPMMxowYIBGjx6tYcOGSZKcTqe2bt2qBx98MMnHiYuL0yeffKLBgwcnWv/NN9/on3/+Ub58+XT48GENHDhQJUuWdG//66+/NG7cOJUpU0ZnzpxRlSpV9Nxzz7m3G4ahcePGydPTUyEhIZo0aZKaNGmib7/9VpK0fPlynThxQh4eHpo7d67y5cunAgUK6Omnn77n12TRokUqUqSIypcvr+joaB06dEjFihVL1KZQoULuOZs2bdqkBg0a3HAcq9UqT09P97LNZrvnTLt379b777+v7t27a/LkyZozZ46WLl0qb29vWSwWhYaGqnr16nriiSfc+4SGhipr1qz3dV4AAJA09Klu7tChQ/L397/ptiNHjmjdunUaPXp0ovX303fp2rWrJOmzzz7TmTNn9Pzzz6tFixZ68sknZbFY1LdvX/n7+6tWrVrufYKDg90j4gGkDIpSAG7Kw8ND3bt3186dO+Xn5ydJunjxoqKjo5P0n/O8efO0bt06/fDDD3r00UfVrl0797YffvhB27Zt06RJkyQlTPLZsmVLbdy4UXa7XREREerUqZNWrVrlPvdzzz2nQoUKqWHDhpKkuXPnqkiRIgoICJAkvfTSSxowYID7HNeKMHPmzFG7du3uqtN3vT179mjixImaMWOGHnvsMfdlb8HBwTIMQ1mzZk3UPkeOHIqOjlZwcLDOnj2rvHnz3vEc99PBqlChgl5//XX17NlTkyZN0ttvvy0fHx/39mzZsuny5cuKiYnR2bNnFRgYqIEDB6ps2bKaNGkShSkAAFIYfaob7dixQ9u2bdNnn33mXnfhwgVNnDhR69atk91u18qVK92j06+536JU1qxZVaFCBfXu3Vsff/yxJLknSs+WLZtCQkJ06dIlBQYGasOGDXr//ff1448/qmrVqvd8XgC3R1EKwC3lzZtX69atU2RkpHx8fNz/aRtXb70rSVOmTNEPP/wgX19fDRw4UOXLl5cktW3bVuXLl1evXr00evRovfDCC5o6daokaejQoVq0aJH7GPny5VPVqlU1e/ZsdenSRV9//bWaNWvm7jxJ0uuvv65Bgwa5O1AXLlzQqVOn1Lp1a1ksFuXPn9/96WNyqlixonr06KGff/5ZFStWdBd8rr0W/3WtsxQbGyuXyyWHw3HHc9hsNi1btsz93K7NiWC1WnXx4kWFhYVp8+bNkuSez+r6fQcOHKi9e/fqzJkz6tevn9asWaODBw8qW7Zs8vDwUHx8vBYvXqySJUsqf/78+u2335KUCwAAJA/6VNKWLVs0ceJE97QC69atk7e3t3t7njx51KNHD0VGRurEiRM3jEaXEvo9bdq0kaen5w19pOv7TyEhITdcBnmtuNa9e3cNHz5cUVFRmjRpkvz8/GS323X+/Hnt2rVLZcuWVcGCBdWqVSv17Nkz2V8HAIkx0TmAW/rrr79Uv359TZ8+XVJCZ8Futyea5PLFF19UVFSUatSo4e48/dfAgQO1bNky/fHHH7py5Yr+/vvvRMPKJalMmTLasmWLpITL3v77KVyZMmW0detWd+eta9euOnHihB588EENHTpUgYGBKl26dLI99/9q166dZs+e7V7OmTOnbDabrly5kqhdZGSkrFar/Pz85Ovrq9DQ0Dse+9qcUmvXrtXatWtVqlQprVy5UmvXrtXIkSPVsWNH97YKFSok6sBK0pkzZ/TWW29p5MiRCg0NldVq1dNPP63nn39e7du3V/ny5fXwww+revXqmjBhgnr27HnDMQAAQMqhTyXVqFFDPXr0UL9+/TR48OBE8z1dr23btpo/f76cTucN267NKXWzPtL1/aePP/74pn2d0aNHq2bNmipevLhOnz6tSpUqqUuXLurYsaPat28vh8Ohxo0bK2vWrKpTp06iG9MASBkUpQDc1KpVq9SkSRO99tpr+vzzzyUljA56+OGH3XMmXZM9e3ZlyZLllsey2WwqUaKENm/eLMMwZLPZZLUm/vNjt9vlcrkkJdxNzm6337D9+s5F1qxZNXfuXK1evVpOp1M1atTQ/Pnz7+s5387TTz+t3377TeHh4ZIkT09PPfTQQzp58mSidoGBgSpTpox7++HDh284Vnh4eKL9btbpup3rXwen0+m+40zv3r21cOFClSlTRgULFnS3KV++vHbv3q05c+Zo7ty5ev3112850gsAACQv+lR3p3DhwnrggQf022+/3bDtbvpM/y1KTZs2TcOGDdPChQv13nvvKSgoSHXr1nVvf/jhh7Vnzx6Fh4crICBALVq0UIUKFe79iQBIEopSAG5q+fLlatasmRo1aiSbzaZVq1ZJktq3b+9+fDdCQ0Pl6+urbNmyqVChQjp79myi7UeOHHFPdunv76+jR4/esL1SpUruYsry5cslSUWKFNEHH3ygrVu36u23377hvNd3vJxOpwIDA+86u5TQYXv00UcTDZFv27ate8j4Ndu3b3ffHe/JJ5/Ur7/+esOxfvrpp0SXz91PUcpms2n06NGaNWuWvv32W7344osqUKBAovYNGzbU//73Pw0bNkzr16/XI488ouPHj9/VOQEAwL2hT3X32rdvn2iE+jX3U5QKCAjQBx98oIULF+r9999X3bp13XdClBIusfT09FS9evX0+OOPa9y4cfLw8KDPBKQwilIAbnDw4EGVKFHC3Vl57bXXNG7cOEkJQ8uXLVumS5cuudvfqVOyY8cOXbp0Sc2bN5ckvfvuuxo/frx7e1hYmLZu3aouXbpIknr16qUffvhBUVFR7jZff/213n//fffyrFmzEnUSChQooMqVK99w7pIlS7pvN7x27VpFR0cn5SW4qf9ewtenTx+tWbPGPXoqOjpaK1asUN++fSUldH78/Pzcd6+RpJMnT+r06dPKnz+/e921TzOT6r/tAwICVK1aNRmGoUOHDmnevHlauHChnE6nXC6X9u3bp/379+vTTz91D/GfO3fu3T15AABw1+hT3ZuAgAAtXbr0hnPcTZ/pv22zZMmiAQMGyNvbW+Hh4Vq/fr1mzpypAwcOSEq4xDImJkYOh0PDhw+XlDDf1tq1a+/vyQC4LSY6B5DIhg0b1Lt3b9WpU8e9LjQ0VD///LOGDh2qESNGaObMmerXr5/KlCmj2NhYvfnmm6pfv74k6fPPP9e5c+c0fvx4VapUSeHh4dq2bZtWrlzpHsHz/PPP6/PPP9eQIUOUK1cunTlzRtOnT5enp6ekhDvYffXVVxowYIAeeOABhYSEqG7dumrSpIk7k4+Pj2rVqqUXXnhBxYsXV1hYmPr373/D8+nXr59ee+01LVmyRFWrVlXjxo2T9Drs3r1bK1asUFRUlLZu3arq1avrySefVLdu3TR27Fj1799fvr6+mjZtmt577z2VKlVKR44c0VdffaXcuXNLShjFtHz5cg0cOFArV66Un5+fChUqpEGDBiU6l9PpvKuJzq//lHDHjh369NNPdfz4cRUrVkxNmzZVixYtlCtXLh05ckSvvvqqqlevroULF6pHjx5atWqVHnzwQf3xxx9Jeh0AAMC9oU+V4JdfftGmTZt0+PBhNW7cWNWqVUu0PSgoSFOmTHF/sNa2bVvly5dPlStX1quvvqqPPvrIfRc+p9OZ5InOS5Qokeg87777rrZt26aYmBjVqFFDTz/9tJ577jkZhqHPP/9cixcv1jfffKPRo0erX79+Gjt2rH7++Wdly5YtSc8TwL2xGMx2CwCmmj59utavX6/JkyffsW3Dhg01fvx49wSo4eHh+uijj9SrVy/ly5dPUsKnsrNmzZLFYlGPHj3c80stWrRIr7zyitq1a6d169bdcCc/AACAtKxkyZJav369ChcufNt2q1ev1uTJkzVnzhz3uunTpytfvnxq2rSpLBaLYmNj9f3332vHjh1q1KiRnn32WUlyzykVGhoqu92uN954Q61bt07R5wVkZhSlAMBkf//9t7JmzapChQrdse21eSRuJTY2Vrt371a1atVuOpn533//rXfeeUcrV65UYGCgsmbNel/ZAQAAUsuaNWvUsGHDO96wJSIiQl5eXrLZbLdsc+DAAeXLl889Cut6LpdLX331lT7++GN16NBBo0aNuu/sAG6OohQAZEIul+uGu/UAAAAgMfpMQMqiKAUAAAAAAIBUR8kXAAAAAAAAqY6iFAAAAAAAAFKdh9kBUpPL5VJgYKCyZct2x8nxAAAADMNQeHi4ChYsmGnnFKH/BAAA7lZS+1CZqigVGBioIkWKmB0DAACkM6dOnbrjLcgzKvpPAADgXt2pD5WpilLZsmWTlPCiZM+e3eQ0AAAgrQsLC1ORIkXcfYjMiP4TAAC4W0ntQ2WqotS1IefZs2enUwUAAJIsM1+2Rv8JAADcqzv1oTLn5AgAAAAAAAAwFUUpAAAAAAAApDqKUgAAAAAAAEh1mWpOqaRwuVyKjY01OwbSAIfDkWlv/w0AAAAA6Rnv7VOW3W6XzWa77+NQlLpObGysjh07JpfLZXYUpAFWq1UlSpSQw+EwOwoAAAAAIIl4b586cuTIofz589/XDWEoSl1lGIbOnj0rm82mIkWKMEImk3O5XAoMDNTZs2dVtGjRTH3XJQAAAABIL3hvn/IMw1BkZKSCgoIkSQUKFLjnY1GUuio+Pl6RkZEqWLCgfHx8zI6DNCBPnjwKDAxUfHy87Ha72XEAAAAAAHfAe/vU4e3tLUkKCgpS3rx57/lSPkqGVzmdTkniUi24XftduPa7AQAAAABI23hvn3quFf3i4uLu+RgUpf6Dy7RwDb8LAAAAAJA+8X4u5SXHa0xRCgAAAAAAAKmOohQAAAAAAEA6FhgYqCFDhsgwDO3bt08ff/zxbduHhITo9OnTidbNmDFDwcHBKRnzBhSlAAAAAAAA0jGr1aqTJ0/KYrEoNjZWFy9evG37pUuXqm/fvonWTZw4UbGxsXrrrbe0aNGilIzrZurd944ePaqVK1dq9OjROnHiRJL2OXHihIYMGaISJUooPj5eo0aNyvTXioaFhalz585asmSJJGnBggV64YUXtGzZMjVo0ECnT59Wnz591KtXLzVu3FihoaH63//+p3z58mnfvn2aN2+eQkJC7nm2/Du5159ZSEiIvv76a/n4+GjDhg0aMGCAqlWrJimhghseHq5Lly4pNjZWI0aMcO+3efNmLV++XLt379YPP/yQ6Ji7d+/WJ598oiJFisjPz0/9+/dP3icLAEAKo/8EAEDGsnXrVrVu3VovvfSSihUrppMnT6patWpyOBx65pln9NFHH8nDw0NbtmxRly5dVK9ePUkJk7rHxMTIx8dHVqtVVmvCuCOr1ep+f+9yuRQaGio/P79E51y6dKnGjRuny5cvK0eOHJISJofPmTOnBg8erDZt2qhVq1Yp/+SNNKBIkSJJbvvoo48aBw4cMAzDMD755BNj0qRJSd43NDTUkGSEhobesC0qKso4cOCAERUVleTjpRXLli0zChUqZJw4ccK9rlixYonazJgxw/24TZs2xv79+93L77//vvHnn3+mWL57/Zm99tprRmRkpGEYhrFo0SJj1apVhmEYxsGDB41nn33W3a5v377GwoULE+175MgRo0GDBonWuVwuo2LFisaFCxcMwzCMPn36GCtXrrzl+dPz7wQApAaXy2VExMSliS+Xy5Uiz/F2fQezpYX+EwAAaU16fR/XoEED49ixY+7lZs2aGWfOnEn03v7ixYvGQw895F6+fPmyUbBgQaNBgwZG7dq1jXz58hkNGjQwqlSpYhQtWtRo0KCBUbFiRaN+/fqJznXkyBGjf//+hmEYxmOPPWZs3LjRMAzDePzxx43w8HAjNDTU+O6774zXXnvNcDqdt8x8u9c6qf0HU0dKXXOtmncnR44cUUhIiB566CFJUsuWLdW2bVt17979pu1jYmIUExPjXg4LC0tyJsMwFBXnTHL75ORtt93Vp5d79uzRCy+8oI0bN6po0aK3bXvw4EFduHBB5cqVc697/fXXdeTIkXvOezt3+zO7ZufOnfL09JS3t7ck6dlnn3Vvy5Ejh6pXr+5eLlu2rI4fP55o/5v9Tq1Zs0YlSpRQ7ty53Vm+/PJLPfbYY/f03AAgMzMMQwETN2nHiRCzo0iSDrz/uHwcaaJbk2rSYv/pXszeclLHLl7RUxULqUJh3xQ9FwAg80lP7+2vV6FCBW3evDnRuly5ciX6P9pms6l06dJau3atLl68qP79+2vq1KnavXu3FixYoJEjR2rt2rVasWJFouNMnDhR77zzjo4fP658+fJp3Lhxev3113Xo0CENGzZMDzzwgEqXLq0GDRrcU/a7ka56b5s3b1aVKlXcy6VKldI///yjmJgYeXp63tB+9OjRGj58+D2dKyrOqXLDfrnnrPfjbjvWcXFxqlOnjpYtW6YOHTrctu3y5ctVv379ROuyZ8+uypUr33a/GTNmaP/+/Tesf/7551W+fPlb7ne3P7NrfvvtN+XOnVu9e/dWZGSkAgMDNXnyZBUqVEj58uXTW2+9JSnhua9Zs0YffvjhbfNL0qZNm1S1alX3cvny5bV169Y77gcAuFFUnNP0glTUiT3yKlJeFmvKXH6eUaRm/+leLP/zrH4/dFHlCmanKAUASHbp6b399cLDw92X1V2zadMm1apVy72c1A+oPDz+zTBnzhz9/fffWrp0qdavX69PP/1UUkLBq23btnrrrbeUN29eSVJUVFSSz3Gv0lVR6uzZs8qVK1eiddmyZdOlS5dUsGDBG9oPHjxYb775pns5LCxMRYoUSfGcqSkyMlJeXl6qWbOm3nnnnTu2P3HihMqUKXPX5+nUqdO9xLvrn9k1x48f19atW/Xzzz8rd+7cWrFihYYNG6Zvv/3W3ebXX3/V+++/r5dfflnFixdPUpbrC2h+fn4KCgq6+ycFAEhk+5Am8nGkXmEoLi5O7wx+SxPmfK5Bg9/WsPeGy9tOYepW6D8BAJC+XLhwQf/884/q1q0rKaGQ9Msvv6hkyZKJ3hPfS8GoatWqyp07t7JmzSq73e6+kkhK6B/s2bNHnp6eOn/+vL788kt99913SXq/fa/SVVEqNjZWhmEkWudyueTl5XXT9p6enrcdjXM73nabDrz/+D3te7/upmP9xx9/qHr16vLz81NsbKzCwsKUPXv2W7aPjIxM1YlN7/Zndk1UVJQCAgLc/0CaNWumN954I1Gbxo0bq1GjRurRo4eyZcump5566q6yJCUHAODOfBy2VLt07syZM2rbtq3++OMPSZLFcN3X0PjMIDX7TwAApDXp5b29JP3444/KkSOHLly4oHnz5snhcEiS2rdvr+PHjytPnjyJ/o+22WzavXu3GjZsqLi4OB05ckQNGzbUlStXdOHCBW3YsEGXL19WixYt3PuUKlVKefLk0fDhw/XJJ59o48aNOnv2rBwOh5xOpwIDA9WmTRsdO3ZMv/32W4r3sdJVUSpfvnw6duxYonVRUVHKmTNnsp/LYrGki7kptmzZokKFCmnOnDnKmTOnNm/erKZNm96yfe7cuXXp0qW7Ps+9Xr53rz+zbNmy3XA3wJtVga1Wq15++WW9+eabdyxK5cuXT8HBwe7ly5cvK3/+/LfdBwCQdvz666/q0KGDLly4IF9fX02bNk1PP/202bHSvNTsPwEAkNakl/f2UsK8j7caldS+fXt169ZNL730knudYRiqVKnSHeeUWr16tXufwMBAtW/fXh06dNB3332nwoULq3nz5vLx8VFERIQuXbqkkJAQPfnkk5o1a5bq1KmTos85ffxkrqpevbq++uor9/I///yjSpUqmRcoDYiPj9cLL7wgSQoJCdHGjRvVtGnTW1Yzq1atqqlTpyZad/bsWZ06dSrR5OH/da+X793rz+xm8z1dGwG2Y8cOzZo1S5988okkycvLS+Hh4UnKMm3aNPfyvn37VLNmzaQ8DQCAiVwul0aPHq1hw4bJ5XKpYsWKWrhwoR544AGzo6UL9J8AAEj/ihcvrqioKJ09e1YFChSQlNBHSorr22XJkkUOh0OFChVSy5YtE9UOatWqpW7dumnWrFmaMWOG6tSpo6ioKPcNyFJCys5YlURxcXHuYeUul0sdOnTQjh07bmhXoUIF+fj46PDhw5KkpUuX3vEubhnZfycobdCggTZs2CApYZKya3fLcblcCg0NlSQ1b95cR44c0alTp9z7zZ8/P9EEqMnpdj+z0NBQPfvsszp58uQN+z3zzDNavXq1rly5Ikn666+/3CPATp48qRMnTrjbbty48YZPyq//nbrm8ccf15EjR3T58uUbsgAA0q6jR49q1KhRcrlc6tq1qzZt2kRBSvSfAADIbNq0aaO5c+e6l5NalLr+vbGvr69Wr16tp556SiEhIfrtt9/c/YcrV65o48aNeuONN1SvXj1dvHhR69atS94n8R+mjpQ6cuSI5s+fr8DAQPXr10/PPfecHn74YW3ZskWnT5++aaFk5syZeu+991SsWDHFxMSoX79+JiRPG0aMGKEDBw4oMjJSPj4+OnPmjH7//Xf99NNPmjhxogYNGqTixYsrPj7e3fn08vLSokWL9NZbb6lQoUKy2Wzq2rXrDZfKJadb/czCw8O1detWBQUFqWjRoon2yZ07t77++mv16dNH1atX1+XLlzV48GBJCQWrw4cPa9y4cbJYLAoKCtKwYcPc+27cuFGTJk3Szp079cknn6ht27YqXLiwHA6Hpk6dqn79+qlgwYIqXry46tWrl2LPGwCQPEqVKqVJkyYpJiYm0ZD1zIr+EwAAGcsff/yho0ePaubMmRoyZIh7/a+//qqLFy9q9uzZ6tixo9q2bauGDRuqfv368vf3l8vlStKcUo8//u+cWvPnz3dftle7dm21aNFC2bNn18cff6xDhw656wX+/v7asmWLSpYsmaLP3WL8dzhJBhYWFiZfX1+FhobeMBl4dHS0jh07phIlSjD5NSTxOwEAtxMZG+++vfL93O74ZgzD0KRJk/TII48kuu2xGW7Xd8gsUvo16PTtFv1+6KI+bVdRz1YunOzHBwBkLpnpfVxoaKgCAgK0atWqW7ZZv369du3apddff12SdOzYMRmGoZIlS8rlcmnJkiU6efKknnjiCZUtW1ZSwpQ5zz//vOLj4/X111/r0Ucfvemxb/daJ7X/kK7mlAIAABlbZGSkevTooRkzZqhw4cLau3ev/Pz8zI4FAACQ5mTNmlWLFi26bZv69eurfv367uUSJUq4H1utVrVq1eqGfapUqaKdO3dqxowZ8vBI2bIRRSkAAJAm/PPPP2rdurX+/PNPWa1W9e7dWzly5DA7FgAAQJpks9mULVu2FDm2t7e3Xn755RQ59vUoSv1HJrqaEXfA7wIApJ6FCxeqa9euCg8PV758+TR37lw1aNDA7FgAAABIQWni7ntpwbWJvmNjY01OgrTi2u9CSk4CDwCZXXx8vPr166eAgACFh4erXr162rVrFwUpAABwXxhkkPKS4zVmpNRVHh4e8vHx0YULF2S322W1Uq/LzFwuly5cuCAfH58Uv4YWADIzm82mo0ePSpIGDBigDz74gL+7AADgnl0/4MTb29vkNBlbZGSkJMlut9/zMej1XWWxWFSgQAEdO3ZMJ06cMDsO0gCr1aqiRYvKYrGYHQUAMhzDMGSxWGSxWDR16lRt2LBBTz75pNmxAABAOseAk5RnGIYiIyMVFBSkHDly3NfVRRSlruNwOFS6dGku4YOkhN8H/oABQPJyuVz68MMPdeDAAc2YMUMWi0W+vr4UpAAAQLJgwEnqyZEjh/Lnz39fx6Ao9R9Wq1VeXl5mxwAAIMMJCQlR586d9dNPP0mSXnjhBTVp0sTkVAAAIKNhwEnKs9vtyTL/MkUpAACQ4nbs2KGAgAAdP35cnp6emjBhAgUpAACQYhhwkj5QlAIAIAUZhqGoOKfZMZJdZGzSnpNhGPrmm2/Up08fxcTEqGTJklqwYIEqV66cwgkBAACQ1lGUAgAghRiGoYCJm7TjRIjZUUwzcOBAjR07VpL01FNPaerUqfLz8zM5FQAAANICZnEGACCFRMU5M3xBqmoxP3nbbz2fQIsWLeRwOPThhx9q8eLFFKQAAADgxkgpAABSwfYhTeTjuP/JINMab7tNFosl0bqzZ8+qQIECkqQGDRro6NGjKlSokBnxAAAAkIZRlAIAIBX4OGzycWTs/3bj4uI0ePBgff3119q2bZsefPBBSaIgBQAAgJvK2L1jAACQKgIDA9WuXTtt2LBBkrRixQp3UQoAAAC4GYpSAADgvqxdu1bt2rVTUFCQsmfPru+++06tWrUyOxYAAADSOCY6BwAA98TlcunDDz9U48aNFRQUpEceeUTbt2+nIAUAAIAkoSgFAADuydSpUzV48GC5XC516dJFmzZtUunSpc2OBQAAgHSCy/cAAMA96dSpk+bMmaM2bdqoW7duN9yFDwAAALgdilIAACBJDMPQ4sWL1aJFCzkcDtntdv3yyy8UowAAAHBPuHwPAADcUWRkpF588UW1bt1agwYNcq+nIAUAAIB7xUgpAABwW4cPH1br1q21d+9eWa1W5cuXT4ZhUJACAADAfaEoBQAAbmnx4sV64YUXFBYWprx582rOnDlq1KiR2bEAAACQAXD5HgAAuEF8fLwGDBigVq1aKSwsTHXr1tWuXbsoSAEAACDZUJQCAAA3OHXqlL7++mtJUr9+/fTbb7+pYMGCJqcCAABARsLlewAA4AYlSpTQ9OnT5XQ61bp1a7PjAAAAIAOiKAUAAGQYhsaMGaOqVauqcePGkqRnnnnG3FAAAADI0ChKAQAkJRQlouKcZsfIUCJj08frefnyZXXp0kVLly5Vnjx5dPDgQeXMmdPsWAAAAMjgKEoBAGQYhgImbtKOEyFmR0Eq27VrlwICAnT06FE5HA6NHDlSfn5+ZscCAABAJkBRCgCgqDgnBakUVLWYn7ztNrNj3ODbb79Vr169FBMTo+LFi2vBggWqUqWK2bEAAACQSVCUAgAksn1IE/k40l4BJT3ztttksVjMjuEWHx+vV155RVOmTJEkPfnkk5o+fTqX7AEAACBVUZQCACTi47DJx8F/DxmZzWZTbGysrFarRo4cqUGDBslqtZodCwAAAJkM7zoAAMgknE6nbLaEUVsTJ07UK6+8orp165odCwAAAJkUH4sCAJDBxcfHa+DAgWrVqpVcLpckKUuWLBSkAAAAYCpGSgEAkIGdPXtW7du31/r16yVJa9asUePGjU1OBQAAADBSCgCADGvdunXy9/fX+vXrlS1bNs2fP5+CFAAAANIMilIAAGQwhmHoo48+UuPGjXXu3DmVL19e27dvV0BAgNnRAAAAADeKUgAAZDC9e/fWwIED5XQ61alTJ23evFllypQxOxYAAACQCEUpAAAymM6dOytbtmyaOHGipk2bpixZspgdCQAAALgBE50DAJABHD58WKVKlZIkVa9eXSdOnJCfn5/JqQAAAIBbY6QUAADpWFRUlF566SVVqFBBu3fvdq+nIAUAAIC0jqIUAADp1JEjR1S7dm1NmTJFMTEx2rx5s9mRAAAAgCTj8j0AANKhH374QV26dFFoaKjy5Mmj2bNnq0mTJmbHAgAAAJKMkVIAAKQj8fHxGjRokJ555hmFhoaqdu3a2rlzJwUpAAAApDsUpQAASEemT5+uMWPGSJLeeOMNrV27VoULFzY5FQAAAHD3uHwPAIB0pEuXLlqxYoXatGmjNm3amB0HAAAAuGcUpQAghRiGoag4p9kxkiQyNn3kzIwMw9B3332nDh06yNvbWzabTfPmzTM7FgAAAHDfKEoBQAowDEMBEzdpx4kQs6MgHQsNDdULL7ygJUuWaNOmTfrmm2/MjgQAAAAkG4pSAJACouKc6bIgVbWYn7ztNrNjQNKePXsUEBCgw4cPy+FwyN/fX4ZhyGKxmB0NAAAASBYUpQAghW0f0kQ+jvRR6PG22yh6pAFTp05Vz549FR0drWLFimn+/PmqVq2a2bEAAACAZEVRCgBSmI/DJh8Hf25xZ9HR0erdu7cmT54sSXriiSc0Y8YM5cqVy+RkAAAAQPKzmh0AAAAkuHDhghYtWiSLxaIRI0bop59+oiAFAACADIuP7gEASCOKFCmiOXPmyGKxqEmTJmbHAQAAAFIURSkAAEwSHx+voUOHqnbt2mrZsqUk6bHHHjM5FQAAAJA6KEoBAGCCc+fOqUOHDlq7dq1y5MihI0eOKGfOnGbHAgAAAFINRSkAAFLZ77//rnbt2uns2bPKmjWrJk6cSEEKAAAAmQ4TnQMAkEoMw9DHH3+sRo0a6ezZsypXrpy2bdumdu3amR0NAAAASHWMlAIAIBXExcWpffv2WrRokSSpY8eO+vrrr5U1a1aTkwEAAADmYKQUAACpwG63K2/evHI4HPryyy81c+ZMClIAAADI1BgpBQBACoqOjpaXl5ckady4cXrllVdUqVIlc0MBAAAAaQAjpQAASAHR0dHq3r27WrRoIafTKUny9PSkIAUAAABcxUgpAACS2dGjRxUQEKBdu3bJYrHo999/V8OGDc2OBQAAAKQpjJQCACAZ/fjjj6pSpYp27dqlXLly6ZdffqEgBQAAANyEqSOlQkJC1LdvXxUvXlzBwcEaM2aMe96NW/nxxx+1Z88e+fr66vTp0+rbt6/y58+fSokBALi5+Ph4DRs2TKNHj5Yk1axZU/PmzVORIkVMToaMhv4TAADIKEwtSvXr10/t27dXs2bNtHjxYo0YMUKjRo26ZftLly5p7ty5mjlzpiTp7Nmz6tevn2bNmpVakQGkc4ZhKCrOmeLniYxN+XMgbenZs6cmT54sSerTp48++ugjORwOk1MhI6L/BAAAMgrTilJXrlzR6tWr3R34Zs2aqVevXhoxYoSs1ptfVXj06FFly5bNvVygQAGdO3fulueIiYlRTEyMezksLCyZ0gNIjwzDUMDETdpxIsTsKMiA+vTpo6VLl+rzzz9Xu3btzI6DDIr+EwAAyEhMm1Nqz549Klu2rLsD5e3trezZs+vo0aO33KdChQpatWqVfv75Z0nS3r171aBBg1u2Hz16tHx9fd1fXEIBZG5Rcc5UL0hVLeYnb7stVc+J1GEYhnbt2uVerlChgo4dO0ZBCimK/hMAAMhITBspdfbsWeXKlSvROj8/PwUFBalUqVI33cfLy0tLly5VvXr1VKNGDfn7++v999+/5TkGDx6sN998070cFhZGxwqAJGn7kCbycaR8scjbbpPFYknx8yB1hYWF6cUXX9QPP/yg9evXq1atWpIkHx8fk5Mho6P/BAAAMhLTilKxsbEyDCPROpfLdduJOl0ul8aPH68tW7bol19+0fDhw9WiRQvVrFnzpu09PT3l6emZrLkBZAw+Dpt8HKZOq4d0at++fWrdurUOHToku92uQ4cOuYtSQEqj/wQAADIS096R5cuXT8HBwYnWXb58+bZ3glm2bJlKlSrl/ipevLh69+6tbdu2pXRcAAA0ffp09ejRQ1FRUSpSpIjmz5+vGjVqmB0LmQj9JwAAkJGYNqeUv7+/9u/f7/60LyIiQi6XSwUKFLjlPgcPHlS5cuXcy08++eQNHTMAAJJbdHS0XnnlFXXp0kVRUVF6/PHHtXPnTgpSSHX0nwAAQEZiWlHKz89Pjz76qNasWSNJWr58uTp37izDMNShQwft2LHjhn3q1aunLVu2uJcvXLig8uXLp1pmAEDmNGfOHE2aNEkWi0Xvvfeeli1bpty5c5sdC5kQ/ScAAJCRmDqhyvjx4zVgwABt3LhRQUFBGjt2rGJjY7VlyxadPn1aVapUSdS+Zs2aOnnypD799FN5e3vrzJkz+uqrr0xKDwDILLp06aI//vhDrVu31uOPP252HGRy9J8AAEBGYWpRKkeOHPrmm29uWH+72xq3bds2JSMBACCn06nPPvtML7/8srJmzSqLxaJJkyaZHQuQRP8JAABkHKZdvgcAQFoUFBSkpk2bql+/furevfsNdzoDAAAAkDy4HzoAAFdt3LhRbdu2VWBgoLJkyaKnn35aFovF7FgAAABAhsRIKQBApmcYhj799FM1bNhQgYGBeuihh7Rt2za1b9/e7GgAAABAhsVIKQBAphYWFqaXXnpJCxYskCS1b99e33zzjbJmzWpyMgAAACBjY6QUACBTi4iI0O+//y673a4vvvhCs2fPpiAFAAAApAJGSgEAMrUCBQpowYIFstvtqlGjhtlxAAAAgEyDkVIAgEwlOjpaPXv21Jw5c9zr6tatS0EKAAAASGUUpQAAmcbx48dVt25dTZw4US+//LKCg4PNjgQAAABkWhSlAACZws8//yx/f3/t2LFDOXPm1Lx585QzZ06zYwEAAACZFnNKAchQDMNQVJzzptsiY2++Hhmb0+nUu+++q1GjRkmSqlevrvnz56to0aImJwMAAAAyN4pSADIMwzAUMHGTdpwIMTsK0oi4uDg1b95cq1evliT16tVLH3/8sTw9PU1OBgAAAIDL9wBkGFFxziQVpKoW85O33ZYKiWA2u92uihUrysfHR7Nnz9b48eMpSAEAAABpBCOlAGRI24c0kY/j5oUnb7tNFosllRMhtRiGofDwcGXPnl2SNHr0aL3yyisqXbq0yckAAAAAXI+iFIAMycdhk4+DP3GZTVhYmLp166bTp09r7dq1cjgcstvtFKQAAACANIh3bACADOHPP/9U69at9c8//8hut2vz5s2qX7++2bEAAAAA3AJzSgEA0r2ZM2eqRo0a+ueff1S4cGGtX7+eghQAAACQxlGUAgCkWzExMerZs6c6deqkyMhIPfbYY9q5c6dq1qxpdjQAAAAAd0BRCgCQbr3yyiuaOHGiLBaLhg0bpuXLlytPnjxmxwIAAACQBBSlAADp1ttvv60SJUro559/1vDhw2Wz3fyOiwAAAADSHiY6BwCkG06nUxs3bnTPF1WmTBn9/fffstvtJicDAAAAcLcYKQUASBcuXLigJ554Qo0aNdKvv/7qXk9BCgAAAEifGCkFAEjzNm3apDZt2ujMmTPy8fFRcHCw2ZEAAAAA3CdGSgEA0izDMPTZZ5+pfv36OnPmjMqWLautW7eqTZs2ZkcDAAAAcJ8YKQUASJPCw8PVrVs3zZs3T5LUtm1bTZ48WdmyZTM5GQAAAIDkwEgpAECa9MMPP2jevHny8PDQZ599pjlz5lCQAgAAADIQRkoBANKk5557Tnv27NGzzz6r2rVrmx0HAAAAQDJjpBQAIE2IiYnRsGHDFBISIkmyWCz66KOPKEgBAAAAGRQjpQCYwjAMRcU5k/WYkbHJezyknhMnTqhNmzbatm2b9uzZoyVLlshisZgdCwAAAEAKoigFINUZhqGAiZu040SI2VGQBqxYsULPPfecgoOD5efnp1deeYWCFAAAAJAJcPkegFQXFedM0YJU1WJ+8rbbUuz4SB5Op1PvvvuumjdvruDgYFWtWlU7d+5U8+bNzY4GAAAAIBUwUgqAqbYPaSIfR/IWkLztNkbapHEXL15Ux44dtWrVKklSjx49NG7cOHl6epqcDAAAAEBqoSgFwFQ+Dpt8HPwpymwsFov+/vtv+fj46Ouvv9bzzz9vdiQAAAAAqYx3ggCAVGEYhnsEW65cubR48WI5HA6VL1/e5GQAAAAAzMCcUgCAFHflyhV17NhR3377rXudv78/BSkAAAAgE6MoBQBIUQcOHFC1atU0Z84c9e3bV8HBwWZHAgAAAJAGUJQCAKSY77//XtWrV9fBgwdVsGBBLV++XDlz5jQ7FgAAAIA0gKIUACDZxcTEqFevXurYsaMiIiLUuHFj7dq1S3Xq1DE7GgAAAIA0gonOAQDJKi4uTg0bNtTmzZslSe+8846GDx8um81mcjIAAAAAaQlFKQBAsrLb7WrWrJn+/vtvzZgxQ08++aTZkQAAAACkQVy+BwC4b06nUxcuXHAvDx06VPv27aMgBQAAAOCWKEoBAO7LxYsX1bx5czVt2lRRUVGSJKvVqkKFCpmcDAAAAEBaRlEKAHDPtmzZIn9/f61cuVJ///23du7caXYkAAAAAOkERSkAwF0zDEPjx49XvXr1dOrUKZUpU0Zbt27l7noAAAAAkoyiFADgrly5ckUdO3ZU7969FRcXp4CAAG3btk3ly5c3OxoAAACAdISiFADgrrz66quaM2eOPDw89Omnn2revHnKnj272bEAAAAApDMeZgcAAKQvI0eO1J49e/Tll19yuR4AAACAe0ZRCkCyMAxDUXHOJLWNjE1aO6QNsbGxWrlypVq0aCFJKlq0qHbt2iWrlcG2AAAAAO4dRSkA980wDAVM3KQdJ0LMjoJkdvLkSbVt21ZbtmzR0qVL1bJlS0miIAUAAADgvvGuAsB9i4pz3lNBqmoxP3nbbSmQCMlh5cqV8vf315YtW5QjRw55ePA5BgAAAIDkwzsMAMlq+5Am8nEkrdDkbbfJYrGkcCLcLZfLpREjRmj48OEyDEP+/v5asGCBSpQoYXY0AAAAABkIRSkAycrHYZOPgz8t6dXFixf1/PPP65dffpEkvfzyy/rss8/k5eVlcjIAAAAAGQ3vHAEAbmvWrNEvv/wib29vTZw4UZ07dzY7EgAAAIAMiqIUAMCtTZs2GjlypJ566ilVqFDB7DgAAAAAMjAmOgeATOzKlSvq06ePgoKC3OveeecdClIAAAAAUhwjpQAgk/rrr78UEBCgAwcO6O+//9aKFSuYeB4AAABAqmGkFABkQnPnzlW1atV04MABFShQQEOHDqUgBQAAACBVUZQCgEwkNjZWffr0Ufv27RUREaFGjRpp165dqlu3rtnRAAAAAGQyFKUAIJM4e/asGjRooC+++EKSNHjwYK1cuVL58uUzORkAAACAzIg5pQAgk8iSJYuCg4OVI0cOTZ8+XS1btjQ7EpBpRUZGymKxyNvb2+woAAAApqEoBQAZmMvlksVikcViUfbs2bV48WJ5eXmpZMmSZkcDMqWJEydq4sSJslqtslqtMgxDrVu3Vr9+/eTp6Wl2PAAAgFTF5XsAkEFdunRJTz75pPtyPUkqV64cBSnABDExMerdu7e8vLy0bds27dy5U9u3b9fWrVtVokQJPf/884qIiDA7JgAAQKqiKAUAGdDWrVvl7++vFStWaOjQoQoJCTE7EpCpffbZZxo6dKheeOEF2e1293qbzaYOHTroiy++0GeffWZiQgAAgNRHUQoAMhDDMPTll1+qbt26OnnypEqVKqXff/9dfn5+ZkcDMq0DBw6oe/fuyps37y3b5M+fX506ddKJEydSMRkAAIC5TJ1TKiQkRH379lXx4sUVHBysMWPGyMvL67b7xMTE6IsvvpDD4dD27dvVrl07Pfnkk6mUGADSroiICL388suaPXu2JOnZZ5/Vd999J19fX5OTAZlbuXLlktSuSJEiSWpH/wkAAGQUphal+vXrp/bt26tZs2ZavHixRowYoVGjRt12n+HDh+uNN95Q3rx5tWHDBp0+fTqV0gKQEkbiRMU5E62LjHXeojVSS1xcnOrUqaM9e/bIZrPpf//7n958801ZLBazowFIZvSfAABARmFaUerKlStavXq1Jk+eLElq1qyZevXqpREjRshqvflVhRcuXNCRI0fcw9/r1q1723PExMQoJibGvRwWFpZM6YHMyTAMBUzcpB0nmJ8orbHb7erUqZOCgoI0d+5c1atXz+xIAO7Czp075e/vf8d29J8AAEBGYtqcUnv27FHZsmXdHShvb29lz55dR48eveU+69atU4ECBfTWW2+pe/fueuyxx/Tnn3/esv3o0aPl6+vr/krqsHgANxcV57xtQapqMT95222pmChzi42NTTTa4c0339S+ffsoSAFpjNPp1OjRo29Yf+7cOR06dEhTp05V//79k3Qs+k8AACAjMa0odfbsWeXKlSvROj8/PwUFBd1yn+PHj+vnn39Wt27d9M033+izzz5T3759b9l+8ODBCg0NdX+dOnUq2fIDmd32IU104P3HE33N71GLy8VSyenTp9WwYUM99thjCg8PlyRZLJYb/q4CMF9gYKDGjRun06dPq3379u71LVu21MmTJ9WlSxcZhpGkY9F/AgAAGYlpl+/Fxsbe0AFzuVy3nagzKipKTZo0UalSpSQlTBx64cIFRUdH33Q/T09PeXp6Jm9wAJIkH4dNPg5Tp6XLtFavXq0OHTro4sWL8vX11V9//aXq1aubHQvALRQpUkTlypVT4cKF1bZtW02dOlXly5fX6NGj1bhxY0lKckGf/hMAAMhITBsplS9fPgUHBydad/nyZeXPn/+W+2TLlk02W+JLg/z8/JjrAECm4HK5NGLECDVt2lQXL15U5cqVtXPnTgpSQDpwrehUqlQp1ahRQ8WLF5fL5dLIkSMlKckjpeg/AQCAjMS0opS/v7/279/v7oRFRETI5XKpQIECt9ynfPny+vvvvxOti46OVp48eVI0KwCY7dKlS2rRooWGDRsmwzDUrVs3/fHHHypZsqTZ0QAkkdPpVN++ffXnn3/KYrFo2rRp6t69u6Skj5Si/wQAADIS04pSfn5+evTRR7VmzRpJ0vLly9W5c2cZhqEOHTpox44dN+xTv359HT161D23weXLl1WhQgXmsAGQ4b3++utavny5vLy8NGXKFH3zzTe3vVwHQNoQHh6uTp066fTp09q3b59Wrlyp2bNna8+ePRo4cKDGjRun9957T8ePH9f777+vd999V19++eUtj0f/CQAAZCSmTggzfvx4DRgwQBs3blRQUJDGjh2r2NhYbdmyRadPn1aVKlUStXc4HJo/f77efvtt1apVS8HBwTe9mw0AZDRjx47VyZMn9fnnn6tSpUpmxwGQRKtWrVL37t116tQpvfvuu3r99dfVtGlTnTlzRhcuXJC3t7eefPJJ/fTTT2revLmcTucdj0n/CQAAZBQWI6mTGGQAYWFh8vX1VWhoqLJnz252HCDdiYyNV7lhv0iSDrz/OBOdp6CIiAgtWbJEzz33nNlRgEwtufoOjz76qH777TdNmDBBixYt0urVqzVixAj9888/mjlzpho1auQe/ZTWpHT/qdO3W/T7oYv6tF1FPVu5cLIfHwAApL6k9h94RwkAaczff/+t1q1ba//+/bLZbIluIQ8gfevVq5dKlSqlgwcPqkOHDnK5XJKSPqcUAABARmLanFIAgBvNnz9fVatW1f79+5U/f34VLFjQ7EgAkolhGBo4cKBWrVqlhx56SLNmzVLZsmXNjgUAAGAailIAkAbExcWpb9++atu2ra5cuaIGDRpo165dql+/vtnRACSTM2fO6KGHHtLYsWMlSbVq1VKXLl0kSVFRUWZGAwAAMAWX7wGAyU6fPq127drpjz/+kCQNGjRII0eOlIcHf6KBjGLq1KkqXLiwunbt6l73+OOPK0eOHJKkbNmymZQMAADAPLzjAQCT7dy5U3/88Yd8fX01bdo0Pf3002ZHApDMihYtetP1NWrUkCStXLkyNeMAAACkCXd1+V58fPwttwUGBuqDDz6470AAkNk89dRT+uKLL7Rjxw4KUkAGExkZaXYEAACANCvJI6VOnjypFi1aaO/evTp06JBKly4tSZoxY4ZcLpfy58+v1atX6+23306xsACQEQQHB+uNN97QqFGjVKRIEUnSa6+9ZnIqAClh5MiR2r59u7y8vGQYxm3bxsfHK1u2bPrmm2/k6+ubSgkBAADMk+SiVOHChSVJp06dUufOnbVs2TI5HA7NmzdP8+bNk7e3tz788MMUCwoAGcH27dvVpk0bHT9+XKdPn9Zvv/1mdiQAKehuR5HXrVtXBw4cUK1atVIoEQAAQNqR5KKU1WpV7ty5VaRIEa1YsUJTpkxRtWrVNHnyZDkcDkmSxWJJsaAAkJ4ZhqFJkyapT58+io2N1QMPPKBPPvnE7FgAUsnKlSt1/Pjxm97AwOl0qmTJkmrcuLFeeeUVClIAACDTuOuJzg3D0AcffKChQ4cqa9as6tGjh7Jnz64xY8bccVg6gLTJMAxFxTnv2C4y9s5tcKOIiAj17NlTM2bMkCQ988wz+u6779x33QKQ8WXJkkXZs2eXzWaTxWKRxWKRYRiKj4+XYRjuuaeef/55k5MCAACkniQXpSIiIiQljIYqWbKkevXqpbFjx6py5cp66qmn3NsApC+GYShg4ibtOBFidpQM6dSpU2revLn+/PNP2Ww2jR49Wv379+fvJZCJxMTEKGfOnKpTp44k6ZtvvlH37t21a9cujRkzRt9//727LX8bAABAZpKku+/t2bNHtWrV0smTJzVw4EC98sorKlmypCZPnqw6deqoe/fuatGihfbt26cWLVqoadOm6ty5c0pnB5AMouKcd12QqlrMT952Wwolylhy5coli8Wi/Pnz69dff9WAAQN40wlkMhs3blSnTp20ZcsWHT16VIMGDZIk+fr66tSpUyanAwAAME+SR0rt2rVLTZo0UeXKlfXuu+/Ky8tLbdu21bfffqumTZuqT58+atSokX766aeUzAsgBW0f0kQ+jjsXm7ztNgortxEXFyebzSar1SofHx8tWbJE3t7eKlCggNnRAJjg0Ucf1ebNmzV9+nSVKlVKlSpVkiSVLFnSPS8nAABAZpSkolTFihUlJQwp79Chg/bv368uXbrozTfflL+/v1auXJmiIQGkDh+HTT6Ou55qDtc5c+aM2rVrpyeeeELvvPOOpIQ3ngAyp2+++UYnTpyQw+GQYRg6deqUjh8/ro8//lg5c+bU5cuXFR4ermzZspkdFQAAINXd07tPX19fjRo1SidOnFChQoU0adIkScyDACBz++2339ShQwcFBQXpwIEDevXVV+Xn52d2LAAmqlOnjipWrOie4NwwDP3444+qUaOGLly4oPDwcHXv3l2FCxfWyJEj5eXlZXZkAACAVJOkOaWuFx0drTfeeEMul0tlypTR8uXLdeLEiZTIBgDpgsvl0gcffKDHHntMQUFBqlixorZt20ZBCoDKlSun6tWrKyQkRN26dZMkZc+eXbVq1dKzzz6rIkWKaM6cOeratav69OnjvrEMAABAZnBXRamHHnpIV65c0dixY/XEE09Ikrp166aePXtKkoKCgpI/IQCkYSEhIXr66af1zjvvyOVyqWvXrtq0aZMeeOABs6MBSCMOHTqkjRs3atGiRapSpYp8fHwUEpJwg4krV65Ikh5++GF9/PHHGjdunIlJAQAAUtddXb43YcIESVLu3Lnd64oWLaoff/xRktS7d+9kjAYAaVtcXJxq166tgwcPytPTUxMmTNBLL71kdiwAaUzp0qX17rvvupcXLlwoT09PSVKNGjXc67Nly6aXXnpJW7ZsSbQeAAAgo7rry/duxmZLuFvXK6+8khyHA4B0wW6364033lDJkiW1adMmClIAkuRaQUqSvvjii0Tb8ufPT0EKAABkGkkuSp08eVIulytJXwCQUUVGRurQoUPu5Zdffll79+5V5cqVTUwFIL251l9as2ZNovWrVq0yIw4AAIApknT5XmBgoLp16yaHwyGr1SrDMG7a7tpdZQ4cOKBhw4apS5cuyRoWAMx06NAhBQQEKCwsTDt37pSfn58sFouyZMlidjQAadjx48dVvHhx9/LOnTv12Wef6d1331WXLl108uRJSVJERIReeuklbd++XXnz5jUpLQAAQOpJUlGqYMGCWrlypaSEycxz5swpD49/dzUMQzExMbLb7bLZbPryyy/1yCOPpExiADDBokWL9MILLyg8PFz58uXT8ePHubsegDuKiIhQ7dq1VaVKFTmdTm3dulW//vqrTp06pZIlSya6KULv3r01ZMgQClIAACDTuKuJziVp+fLl+v777+VwONzrDMNQXFyc2rVrp65du6patWpcygIgQ4iLi9Nbb72lTz75RJJUr149zZ07VwUKFDA5GYD0IEuWLCpbtqz7pjCNGjVSxYoV3dstFosCAwM1duxYtW7dWk8++aRZUQEAAFJdkotSly5d0nPPPacVK1aoVatWmjVrll588cVExalrqlWrlqwhAcAMgYGBateunTZs2CBJ6t+/vz744APZ7XaTkwFITywWyw2PT58+rWnTpuncuXPauXOnPvroI/eNYwAAADKLJE90niNHDp05c0ZSwqTnI0eO1LPPPqtnnnlG3333naKjo1MsJACY4a233tKGDRuUPXt2LVq0SB999BEFKQDJwuFwyOl0KjY2VtOnT1fv3r0VERFhdiwAAIBUleSilM1mU+7cubVhwwaVK1dOpUuX1rJlyzRr1ixdvHhRLVu21Pfff5+SWQEgVX366adq2bKltm/frmeffdbsOADSqbi4OK1fv17r1q3T5cuXFRUVpTx58ujFF19UkSJFNG/ePPXs2VMvvviiwsLCzI4LAACQapJclLrmhx9+UPv27XX8+HH9/vvvmjFjhgIDA7Vw4UKFhYXp/fffT4mcAJDiQkJCNGHCBPcdRnPlyqWlS5eqdOnSJicDkF7FxsaqefPm2rx5s7Zu3arWrVsrMDDQXXxyuVySpAoVKmj8+PEaM2aMmXEBAABSVZLnlHK5XPLw8HDPpxIcHKw5c+Zo//79GjJkiLJnz65XXnlFhw4d0owZM9SpU6eUzA2kS4ZhKCrOaXaMRCJj01Yes+zcuVMBAQE6duyYPD091a1bN7MjAcgAHA6HBg8efMP6n376SVLC/wsrVqxQvnz5VLlyZT399NM6dOgQxXAAAJApJKkotWfPHr355pvq0qWLPDw8dPnyZeXMmVOvvvqqDMPQypUr3XeiKl26tOLi4nTp0iXlypUrRcMD6YlhGAqYuEk7ToSYHQXXMQxD3377rV577TXFxMSoRIkS8vf3NzsWgAxmx44dKlu2rLJmzaqTJ0+qaNGikqTffvtNHh4eGjlypP744w/16tVL58+fNzktAABA6khSUapkyZIaOHCg1qxZo6ZNm+rSpUuyWCwqVKiQpIQ3dRMmTJAkOZ1ORUZGqk2bNnr11VdTLjmQzkTFOdN0QapqMT952zPXnZ8iIyPVq1cvTZ06VZLUsmVLTZs2TX5+fuYGA5ChTJ8+XbNnz9bw4cNVo0YNTZ48Wbt27Up0Vz7DMHT69GnVqVNHlSpVMi8sAABAKkpSUSpbtmx6/PHH9fjjj0uSdu/erZkzZ6pQoULq06cPtzAG7tL2IU3k40hb/2687bZEb5AyukOHDikgIEB79+6V1WrVqFGjNHDgQFmtdz3VHgDcVs2aNdW5c2f3crVq1TR8+PBM9TcXAADgZpI8p9T1KlWqpEqVKikoKEjLly9XixYtkjsXkKH5OGzycdzTPz8kk2PHjmnfvn3Kmzev5syZo0aNGpkdCUAGVaZMmUTLefPmVUxMjLy8vExKBAAAkDbc17vivHnzUpACkC41bdpUU6dOVZMmTVSwYEGz4wDIRGrUqGF2BAAAgDQhydepXLp0SWPHjr1ju5MnT970LjMAYKbAwEC1bNlSR44cca/r3LkzBSkAAAAAMEmSi1ITJ07Uzz//7F4OCQlR8+bNb2j33XffacuWLcmTDgCSwdq1a+Xv76+ffvpJL774otlxAGQyoaGhSW4bEpJ2b4gBAACQ3JJclHrnnXdkGIZ72WKxaN++fTe0e/fdd5MnGQDcJ5fLpQ8//FCNGzfW+fPnVaFCBU2ePNnsWAAyGW9vb02fPv2O7SZOnKgsWbKkQiIAAIC04a5uM3X9XWJy5MihUqVKJXsgAEgOISEhevbZZzV48GC5XC517txZmzdvVunSpc2OBiCTcTgcatCggQYPHqywsLAbtsfFxem9995To0aN5HA4TEgIAABgjvua6JxbGQNIi44dO6YmTZro6NGj8vT01BdffKFu3brxNwuAaYoVK6a+ffuqd+/estlseuCBB+Tp6anjx4/rwoULGjVqFB/2AQCATOeuilJ//fWXez4WwzASLV9z/SV+AGCGggULKnfu3HK5XFqwYIGqVKlidiQAUN68eTVt2jSdOHFCu3fvVmxsrJ566imVKVPG7GgAAACmuKuiVPHixRPNGfXee+/d0MYwDCYSBpDqoqKiZLfb5eHhIU9PTy1atEje3t7KmTOn2dEAIJFixYqpWLFiZscAAAAw3V0Vpby9velEAUhzDh8+rICAAD3xxBMaPXq0JKlQoUImpwIAAAAA3M49zym1fv16FS9e/Ib1XL4HIDUtWbJEXbp0UVhYmM6dO6eBAwfKz8/P7FgAAAAAgDu4q6KU0+mUJEVHR2vSpElMGgzANPHx8Ro8eLDGjh0rSapTp47mzp1LQQoAAAAA0om7Kkpdu42xl5eXZs6cect29evXv79UAHAbZ8+eVfv27bV+/XpJ0ptvvqkPP/xQdrvd5GQAAAAAgKS6q6LU2rVrk9Tu4sWL95IFAO4oLi5O9erV05EjR5QtWzZNmTJFAQEBZscCAAAAANwl69009vX1ve32kJAQSXKPXgCA5Ga32zV8+HCVL19e27dvpyAFAAAAAOnUXRWlNmzYcMtt8+fPV8eOHSVJuXPnvr9UAHCdy5cva+/eve7l5557Tjt27FCZMmVMTAUAAAAAuB9JLkp9//33evXVV2+5vUGDBoqJidG5c+fUoUOHZAkHALt27VKVKlX0xBNP6Pz58+71DofDxFQAcHcuX758y2179+7V66+/nnphAAAA0ogkF6U6dOignDlzKjo6Wn/88YdOnjyZ6Cs6OlqGYWjKlCl65ZVXUjIzgEzi22+/Va1atXT06FHZ7XZduHDB7EgAcNeOHz+ucuXKSZL++OMP9/oPP/xQX3/9teLi4rRv3z6z4gEAAJjmriY6t1qtOn78uN577z3t27dPTZo00blz5/Tnn3/qsccek8Vikc1mU8OGDVMoLoDMICoqSq+99pqmTJkiSWrevLlmzJihnDlzmpwMAO5e8eLFVbhwYZ09e1ZDhw7VlClTlC1bNh04cEDTpk2TxWIxOyIAAIAp7mpOKUl68MEH9fbbb+vBBx/U1KlTNWjQID300EOaPn26zpw5455XCgDuxZEjR1S7dm1NmTJFVqtVI0eO1I8//khBCkC6ljVrVhUoUEC//PKLli1bpn/++Ueffvqp+yYxAAAAmVGSRkr9+eefOnPmjKKjoxUbGyuLxZLo65qCBQuqSJEiKRYWQMY3atQo7d69W3ny5NH333+vxo0bmx0JAJJFfHy8nn/+eU2cOFE5cuRQq1atVLVqVb399tsyDMPseAAAAKkuSSOlTp48qWXLlunYsWNq27atIiIibtqO4ecA7te4cePUqVMn7dq1i4IUgAzh0KFDkiQPDw917dpVXbp0UWBgoDp16qRnnnlG0r99KMMwFBcXZ1ZUAACAVJWkolTz5s31+eef68EHH9SSJUuUJUuWm7bbsWOH/ve//yVrQAAZ29mzZzVy5Ej3KIHs2bNr+vTpKlSokMnJAOD+rV+/Xj179tSJEyfUoUMHNWrUSPXq1dPEiRNVqlQpDRkyRC1bttS+ffvUokULNW/e/LZ3OwYAAMhI7mqic4vFIsMwtGjRIp06dUqff/65Dh065H5coUIF7d27V7GxsdyuHcAdrVu3Tu3bt9e5c+eUPXt29enTx+xIAJCsihYtqtWrV6tRo0bq3bu33n77bWXJkkV9+vTRyJEjVaNGDQ0aNEiNGjXSTz/9ZHZcAACAVHVXE50bhqErV66oZs2aGj16tIoWLarHHntMH330kYoVKyaHw6H//e9/WrhwYUrlBZABGIahjz76SI0bN9a5c+f08MMP6/HHHzc7FgAku+LFi0tK+GCvdu3aevPNN7Vx40Z5eHioRYsWunz5sqn5AAAAzHRXI6UuXryobNmyqUOHDjfd/umnn6pw4cLKkSNHcmQDkAGFhobqhRde0JIlSyTJPenvrS4LBoCM5NKlS+rUqZPCw8Plcrk0evRoSczLCQAAMqe7Gik1ZcqU224PCwuTJNWtW/feEwHIsPbs2aMqVapoyZIlcjgc+uqrrzR9+nQKUgAyhYiICI0YMUL+/v4qUqSItm7dql27dpkdCwAAwDR3VZSqVq3abbevWrVKkpQtW7Z7TwQgwwoPD9fx48dVrFgxbdiwQT169GB0AIBMoVGjRoqNjdXXX3+tChUqSJJ69OjhvkFMYGCgmfEAAABMcVeX791Jrly5kvNwADIAwzDchae6detqwYIFqlevHn8vAGQqQ4cOvWFd7ty5NWfOHEnSJ598ktqRAAAATHdXI6UA4G4cOXJE9evX1/79+93rnnnmGQpSAPAfzZs3NzsCAABAqkvWkVIAcM0PP/ygLl26KDQ0VD179tT69evNjgQApvvggw/04IMP3nK7n5+fGjVqlIqJAAAAzGPqSKmQkBC98MILeu+999SnTx9FR0ff1b41atRIwXQA7kV8fLzeeustPfPMMwoNDVWtWrU0e/Zss2MBgKl+/fVXxcbGatasWbpy5YrCw8M1ZMgQhYeHa9u2bQoPD1d4eLgGDhyoNWvW3PZY9J8AAEBGYepIqX79+ql9+/Zq1qyZFi9erBEjRmjUqFFJ2nfKlCk6f/58CicEcDfOnTun9u3ba926dZKk119/XWPGjJHD4TA5GQCY5/jx42rbtq0+/vhj5cuXT507d5YkTZs2TV26dNH69etVv359SZKnp+cdR0rRfwIAABmFaUWpK1euaPXq1Zo8ebIkqVmzZurVq5dGjBghq/X2A7g2bdqkqlWrpkZM4K4YhqGoOOdNt0XG3nx9RnH48GHVq1dP586dU9asWfXtt9+qbdu2ZscCANMVL15crVq1kp+fn86dO6eOHTvKMAwdOHBAHTt2VFBQkPLmzSvDMORwONSyZUtlyZLlpsei/wQAADIS04pSe/bsUdmyZd0dKG9vb2XPnl1Hjx5VqVKlbrlffHy8Nm3apDfffPOO54iJiVFMTIx7OSws7P6DA7dgGIYCJm7SjhMhZkcxRfHixVW2bFnlzJlTCxcuvO2cKQCQWfz11186fPiwLBaLnn76aY0bN859SXOjRo00e/ZsrVu3Tg0aNEjS8eg/AQCAjMS0OaXOnj17wx24/Pz8FBQUdNv9ZsyY4R72fiejR4+Wr6+v+6tIkSL3nBe4k6g4Z5IKUlWL+cnbbkuFRCkvNDRUsbGxkiQPDw/Nnz9fW7ZsoSAFAFcZhqEff/xRGzZs0Pnz52UYho4dO6YjR44oJiZGZ8+eldOZ9JG09J8AAEBGYtpIqdjYWBmGkWidy+WSl5fXLfc5efKksmXLpty5cyfpHIMHD070iWBYWBgdK6SK7UOayMdx88KTt90mi8WSyomS3549exQQEKBmzZrpiy++kCTlyZPH5FQAkLaUK1dOkyZNUmBgoGbMmKFChQrps88+k9PpVKVKlfT2228rODhYEydO1FNPPaXnn3/+tsej/wQAADIS04pS+fLlU3BwcKJ1ly9fVv78+W+5z++//y6bzaY5c+ZIkiIiIjRnzhzVrVtXhQsXvqG9p6enPD09kzc4kAQ+Dpt8HKbeRyBFTZ06VT179lR0dLSWLl2q999/X35+fmbHAoA06fTp03r++ee1du1aHTx40N0/KViwYKJ2v/32mz744AMNHDhQHh43/z+E/hMAAMhITHvX7O/vr/3798swDFksFkVERMjlcqlAgQK33Oe5555LtPzWW2+pffv2KR0VwFXR0dHq3bt3ogl2Z86cSUEKAG4jf/78unDhgiTpwQcf1LZt2/Tyyy+rSJEicrlcio+P1/bt2zVt2jQNHDhQhw8fvuVl0PSfAABARmLanFJ+fn569NFHtWbNGknS8uXL1blzZxmGoQ4dOmjHjh233d8wjBuGrwNIOUePHlXt2rU1efJkWSwWvf/++1q2bNkNc5sAABLz8PBQ3rx59dVXX+ny5cuqVq2a3nrrLS1dulQ//fSTVqxYoQoVKujJJ5+Uh4fHbeflo/8EAAAyElOvLxo/frwGDBigjRs3KigoSGPHjlVsbKy2bNmi06dPq0qVKjfd79ChQ5oxY4ZOnTqlcePGqWvXrvL19U3l9EDmERcXp8aNG+v48ePKnTu3Zs+erccee8zsWACQrjgcDvXr108eHh46deqUNm7cKCmhUHTo0CH16dNHXbt2VeXKlW97HPpPAAAgo7AYmejjsrCwMPn6+io0NFTZs2c3Ow4ymMjYeJUb9osk6cD7j2e4OaV++OEH/e9//9PcuXOZ8BZAppFcfYdHH31Uixcvlq+vr+Li4rRkyRL9+OOPeu2111SqVCkZhqG4uDj5+PikuT5KSvefOn27Rb8fuqhP21XUs5VvnOMKAACkP0ntP5h2+R6AtO38+fPavHmze/npp5/Whg0bKEgBwD347rvv3KOS7Ha72rRpo+nTpysuLk45c+ZUrly5lD9//jRXkAIAAEhJFKUA3OD3339X5cqV1bJlS506dcq93mrlTwYA3ItffkkYSRsWFqbdu3era9euMgxDderUMTkZAACAeTLW9UUA7othGPrkk080aNAgOZ1OlStXTtHR0WbHAoB0LSgoSMOHD1e9evU0YMAAff/99zp+/Lg6deqk8+fPy9vbW1FRUfLy8pLVatWAAQNUt25ds2MDAACkOIY9AJAkhYaGKiAgQP3795fT6VTHjh21ZcsWlS5d2uxoAJCu5c2bV2XLltVDDz2kyMhIZcuWTZI0YMAAOZ1OLV26VPHx8frxxx/1xBNPJBqhCgAAkJExUgqA9u7dq9atW+vw4cOy2+0aN26cevbsKYvFYnY0AEjXvvrqKy1atEj79u3TU0895f5+q7+v5cuXZ5QUAADINBgpBUATJkzQ4cOHVbRoUW3YsEGvvvoqBSkASAYvvfSSfvnlF1WoUEFLly5V+fLltXTpUhmGkejv7LXHFKQAAEBmwkgpAPr000/l5eWlYcOGKVeuXGbHAYAMw+FwuB9HRUXp/PnzkiSn06lffvlFR48e1bBhw9zfJalZs2aqXbu2KXkBAABSE0UpIBM6duyYJkyYoDFjxshqtcrHx0efffaZ2bEAIMNq2rSpJGnGjBmJ1jVu3FhWq1WtWrWSy+VSXFyccubMaVZMAACAVEVRCshkfvrpJ3Xq1EmXL19Wvnz5NGDAALMjAUCGFhUVpb179+rMmTOqUqWKJkyYoJUrVyo0NFRLly5V9+7dzY4IAABgCuaUApKJYZid4PacTqfeeecdtWzZUpcvX1aNGjXUvn17s2MBQIZntVo1bNgw/fTTT/rhhx9kt9vl5eWlb7/9VlWqVDE7HgAAgGkYKQUkA8Mw1GbiJrNj3NL58+fVsWNH/fbbb5Kk3r17a+zYsYnmOgEAJL81a9aoX79+KlSokHvduXPnNHPmTEVGRur333+XJLlcLjmdTsXExOjNN9/Us88+a1ZkAACAVENRCkgGUXFOHTgbJkkqVyC7vO02kxP9a/PmzWrdurUCAwOVJUsWTZ48mRFSAJBKGjVqpJ07d0qSZs6cqdKlS+vMmTPy8fHRL7/8IpvNph49eqhUqVImJwUAAEh9FKWAZDa/R61Et/k2m91u18WLF/XQQw9p4cKFeuihh8yOBACZzrx583Tp0iXVqVNH+/fvV6tWrdSsWTPFxsZq1qxZ2r17twICAsyOCQAAkKooSgHJLC3Uo1wul6zWhCnjqlSpop9//lk1atRQ1qxZTU4GAJlT27Zt3Y/9/Pzcjx0Oh7p27arIyEgdP35cxYsXNyEdAACAOZjoHMhg9u3bp0qVKmnHjh3udY0bN6YgBQBpRI4cOW5Y5+PjQ0EKAABkOhSlgAxkxowZqlGjhvbt26c333zT7DgAAAAAANwSRSkgA4iOjlaPHj3UuXNnRUVF6fHHH9fChQvNjgUAAAAAwC1RlALSuePHj6tu3br6+uuvZbFY9N5772nZsmXKnTu32dEAAAAAALglJjoH0rF//vlHNWvWVEhIiHLlyqVZs2bp8ccfNzsWAAAAAAB3RFEKSMdKlSqlWrVq6dKlS5o3b56KFi1qdiQAAAAAAJKEohSQzgQFBSlr1qzy8fGR1WrV7Nmz5e3tLYfDYXY0AAAAAACSjDmlgHRk48aNqly5snr16iXDMCRJvr6+FKQAAAAAAOkORSkgHTAMQ+PGjVPDhg0VGBiozZs3KzQ01OxYAAAAAADcM4pSQBoXFhamtm3bqm/fvoqPj1e7du20detW5ciRw+xoAAAAAADcM+aUAtKwP//8U61bt9Y///wju92uTz75RL169ZLFYjE7GgAAAAAA94WiFJBGxcXFqUWLFjpx4oSKFCmiefPmqWbNmmbHAgAAAAAgWXD5HpBG2e12ffPNN3riiSe0c+dOClIAAAAAgAyFohSQhhw/fly//vqre/mxxx7TsmXLlDt3bhNTAQAAAACQ/Lh8D0giwzAUFee86bbI2Juvvxs///yznn/+eTmdTu3cuVMPPPCAJDF/FAAAAAAgQ6IoBSSBYRgKmLhJO06EJPuxnU6n3nvvPY0cOVKSVK1aNdnt9mQ/DwAAAAAAaQlFKSAJouKcSSpIVS3mJ2+7LcnHvXDhgjp27KjVq1dLknr16qWPP/5Ynp6e95wVAAAAAID0gKIUcJe2D2kiH8fNC0/edluSL7fbtGmT2rRpozNnzsjHx0fffPONOnbsmJxRAQAAAABIsyhKAXfJx2GTj+P+/+nMmTNHZ86cUdmyZbVw4UI9/PDDyZAOAAAAAID0gaIUYJIxY8bI19dXAwYMULZs2cyOAwAAAABAqrKaHQDILPbv369u3bopPj5ekuTp6an333+fghQAAAAAIFNipBSQCmbNmqWXX35ZkZGRKlGihN555x2zIwEAAAAAYCpGSgEpKCYmRq+++qqef/55RUZGqkmTJnr55ZfNjgUAAAAAgOkoSgEp5MSJE6pXr56++uorSdLQoUO1YsUK5cmTx+RkAAAAAACYj8v3gBSwZs0aBQQEKDg4WDlz5tTMmTP1xBNPmB0LAAAAAIA0g6IUkALy5s2r6OhoVatWTfPnz1exYsXMjgQAAAAAQJpCUQpIJnFxcbLb7ZKkhx9+WL/99psqVaokT09Pk5MBAAAAAJD2MKcUkAw2bdqksmXLasOGDe51NWrUoCAFAAAAAMAtUJQC7oNhGPr8889Vv359HTt2TMOGDTM7EgAAAAAA6QJFKeAehYeHq0OHDnr99dcVHx+vNm3a6IcffjA7FgAAAAAA6QJzSgH34MCBA2rdurUOHjwoDw8PjR07Vn369JHFYjE7GgAAAAAA6QJFKeAu/fP336pbq4YiIiJUqFAhzZs3T7Vr1zY7FgAAAAAA6QpFKUAJc0NFxTlvuT0y9t9tpcuUUfPmzRUcHKzZs2crb968qRERAAAAAIAMhaIUMj3DMBQwcZN2nAi5ZZv4sCBZPbPK6ukji8WiqVOnytPTUzabLRWTAgAAAACQcTDROTK9qDjnbQtSUUd36OzUN3Rp+WeqUjSHvO02+fj4UJACAAAAAOA+MFIKuM72IU3k40goNjmdTn34wSiNXjBShmGobJYoTe5QjsnMAQAAAABIBoyUAq7j47DJx+GhyLDLCnjmKX0wcoQMw1CPHj30x8aN8vPzMzsiAAAAAAAZAiOlgP/YsmWL2rRpo1OnTsnb21tff/21OnXqZHYsAAAAAAAyFIpSwHXi4uLUvn17nTp1SmXKlNHChQtVvnx5s2MBAAAAAJDhcPkecB273a6ZM2eqffv22rZtGwUpAAAAAABSCCOlkOn99dcBRR7aIp/SNSRJderUUZ06dUxOBQAAAABAxsZIKWRq33//vRrUqa2LP45R7IUTZscBAAAAACDToCiFTCk2Nla9e/dWx44dFRERIc+CZWXz8TU7FgAAAAAAmQaX7yHTOXXqlNq0aaMtW7ZIkga+NVhznDVlsdpMTgYAAAAAQObBSClkKitXrlTlypW1ZcsW5ciRQz/++KPeHf4+BSkAAAAAAFIZI6WQqaxZs0aXLl2Sv7+/FixYoBIlSigyNt7sWAAAAAAAZDoUpZCpjBgxQnnz5lXPnj3l5eVldhwAAAAAADItLt9DhrZlyxYFBAQoJiZGkuTh4aG+fftSkAIAAAAAwGQUpZAhGYah8ePHq169elq4cKE+/PBDsyMBAAAAAIDrmHr5XkhIiPr27avixYsrODhYY8aMueMIlt9++01//vmnJGnv3r0aMmSIihcvngppkV5cuXJF3bt315w5cyRJrVq10htvvGFuKAAAkgn9JwAAkFGYWpTq16+f2rdvr2bNmmnx4sUaMWKERo0adcv2oaGhmjRpkrvYsGvXLvXr108LFy5MrchI4/766y+1bt1af/31l2w2m8aMGaO+ffvKYrGYHQ0AgGRB/wkAAGQUpl2+d+XKFa1evVpNmzaVJDVr1kzfffedXC7XLfc5evSoNm3a5F4uX768Dhw4kOJZkT78/PPPqlatmv766y8VKFhQK1atVo/X+igqzqnI2PjbfDnNjg4AQJLQfwIAABmJaSOl9uzZo7Jly8pqTaiLeXt7K3v27Dp69KhKlSp1030eeeQRzZo1y738119/qXz58rc8R0xMjHuCa0kKCwtLpvRIawzD0GfbrygyXvIs+ohsTw1Qt18ipF9+MTsaAADJhv4TAADISEwbKXX27FnlypUr0To/Pz8FBQXdch+bzaa6deu6lz///HMNHTr0lu1Hjx4tX19f91eRIkXuPzjSlIiICElSVJxTf0dmUf7n/qd87UbIlsXvro9VtZifvO225I4IAECyof8EAAAyEtNGSsXGxsowjETrXC7XHSfqvGbmzJlq3bq1HnnkkVu2GTx4sN588033clhYGB2rDGTlypXq1KmTZsyYoboNH5UkOfIU1/YhTeTjuPvikrfdxtxTAIA0jf4TAADISEwrSuXLl0/BwcGJ1l2+fFn58+e/474rVqyQr6+vnnjiidu28/T0lKen533lRNrjcrk0cuRIvffeezIMQx9//LG7KCVJPg6bfBymzuEPAECKoP8EAAAyEtMu3/P399f+/fvdn/ZFRETI5XKpQIECt91vx44dOn/+vFq2bClJ2r9/f4pnRdpx8eJFNW/eXO+++64Mw1D37t31ww8/mB0LAIBUQf8JAABkJKYVpfz8/PToo49qzZo1kqTly5erc+fOMgxDHTp00I4dO27Y5/Lly1q+fLm6dOkiKWFy6xkzZqRqbphn69at8vf31y+//CIvLy9NnTpVkyZNSvIlCwAApHf0nwAAQEZi6jVO48eP14ABA7Rx40YFBQVp7Nixio2N1ZYtW3T69GlVqVIlUftZs2bp888/18SJEyVJUVFR8vf3NyM6UtnBgwdVt25dxcXFqVSpUlq4cOFt58MAACCjov8EAAAyCovx39kyM7CwsDD5+voqNDRU2bNnNzsO7oJhGHrxxRcVFhamKVOmyNfXN9H2yNh4lRv2iyTpwPuPM6cUACBZ0HdI+deg07db9Puhi/q0XUU9W7lwsh8fAACkvqT2H3jnjjTr4MGDypMnj3LlyiWLxaKvv/5adrudO+QBAAAAAJABmDanFHA78+bNU7Vq1dSpUye5XC5JksPhoCAFAAAA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1QyGVj8BxSt7AYBnc1t8N6CNTt+LiIiIqhbJHrJ+8uQJ7OzUl/i1sbHBs2fP3uocS0tLZGRklHn88uXLYW1tLf40bdr03QOvgGWHz9DQ50fYec2CkVktdHayQW1TY52+JxEREdUchlg/TezeEqO7OmHjyE4wreLz/xAREdH7JdlIqYKCArz+5KBKpapwos63PWfevHmYNWuWuC2Xy3VWWNU2NUbS4n4A+qnt4zP4RERE9L4YWv0EFI+Q8mxuq7PrExERUdUlWVOqYcOGyMxUX0EkKysLjRo1qvCcBw8eqO3Ly8uDrW3ZhYy5uXmZw9J1QSaTce4oIiIi0ilDq5+IiIioZpNsjHSnTp2QmJgo3rnLzc2FSqWCvX35q614enoiLi5O3L5z5w7c3d11HSoRERFRlcD6iYiIiAyJZE0pGxsb9OrVC6dPnwYAREZGYsyYMRAEASNGjEBMTIzGOW5ubrCwsEBycjIA4ODBg5VezpiIiIioumP9RERERIZE0ufNgoODMWfOHJw/fx7Pnj3D6tWrUVBQgMuXL+PRo0dqK8WUCA8PR1BQEJycnKBQKPD3v/9dgsiJiIiIpMH6iYiIiAyFTHh95ksDJpfLYW1tjezsbFhZWUkdDhEREVVxrB2YAyIiInp7la0fuO4uERERERERERHpHZtSRERERERERESkd2xKERERERERERGR3rEpRUREREREREREesemFBERERERERER6R2bUkREREREREREpHdsShERERERERERkd6xKUVERERERERERHrHphQREREREREREekdm1JERERERERERKR3JlIHoE+CIAAA5HK5xJEQERFRdVBSM5TUEDUR6yciIiJ6W5WtoWpUUyonJwcA0LRpU4kjISIiouokJycH1tbWUochCdZPREREpK031VAyoQbd+lOpVHj8+DEsLS0hk8ne+/XlcjmaNm2Khw8fwsrK6r1fnyrG/EuL+ZcW8y8t5l86us69IAjIyclB48aNYWRUM2c9YP1k2Jh/aTH/0mL+pcX8S6uq1FA1aqSUkZERmjRpovP3sbKy4j8qCTH/0mL+pcX8S4v5l44uc19TR0iVYP1UMzD/0mL+pcX8S4v5l5bUNVTNvOVHRERERERERESSYlOKiIiIiIiIiIj0jk2p98jc3ByLFi2Cubm51KHUSMy/tJh/aTH/0mL+pcPcV3/8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",
      "text/plain": [
       "<Figure size 1200x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.metrics import (confusion_matrix, classification_report,\n",
    "                             roc_auc_score, roc_curve, precision_recall_curve)\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "import xgboost as xgb\n",
    "from imblearn.over_sampling import SMOTE\n",
    "from imblearn.combine import SMOTETomek\n",
    "from imblearn.ensemble import BalancedRandomForestClassifier\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 全局设置字体，替换成你想选的字体名，比如 'Microsoft YaHei'\n",
    "plt.rcParams['font.family'] = 'SimSun'\n",
    "\n",
    "# 解决负号显示为方块的问题（可选但推荐）\n",
    "plt.rcParams['axes.unicode_minus'] = False\n",
    "\n",
    "# --------------------------\n",
    "# 1. 数据衔接（使用特征工程输出的数据集）\n",
    "# --------------------------\n",
    "# 假设特征工程后得到以下变量（无需修改，直接承接）\n",
    "# X_train_pca：降维后的训练特征\n",
    "# X_test_pca：降维后的测试特征\n",
    "# y_train：训练集标签（是否造假）\n",
    "# y_test：测试集标签（是否造假）\n",
    "\n",
    "# 查看类别分布（确认不均衡程度）\n",
    "print(\"训练集类别分布：\")\n",
    "print(pd.Series(y_train).value_counts(normalize=True))\n",
    "print(\"\\n测试集类别分布：\")\n",
    "print(pd.Series(y_test).value_counts(normalize=True))\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 2. 采样处理不均衡数据\n",
    "# --------------------------\n",
    "# 采用SMOTETomek混合采样（过采样+去噪）\n",
    "sampler = SMOTETomek(random_state=42)\n",
    "X_train_resampled, y_train_resampled = sampler.fit_resample(X_train_pca, y_train)\n",
    "\n",
    "# 采样后分布\n",
    "print(\"\\n采样后训练集类别分布：\")\n",
    "print(pd.Series(y_train_resampled).value_counts(normalize=True))\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 3. 训练适配不均衡的模型\n",
    "# --------------------------\n",
    "def train_balanced_models(X_train, y_train, X_test, y_test):\n",
    "    # 模型1：带类别权重的随机森林\n",
    "    rf = RandomForestClassifier(\n",
    "        n_estimators=100,\n",
    "        class_weight='balanced',  # 自动平衡类别权重\n",
    "        random_state=42,\n",
    "        n_jobs=-1\n",
    "    )\n",
    "    rf.fit(X_train, y_train)\n",
    "    \n",
    "    # 模型2：XGBoost（手动设置正负样本权重）\n",
    "    scale_pos_weight = (y_train == 0).sum() / (y_train == 1).sum()  # 负样本/正样本\n",
    "    xgb_clf = xgb.XGBClassifier(\n",
    "        n_estimators=100,\n",
    "        scale_pos_weight=scale_pos_weight,  # 关键参数：平衡权重\n",
    "        learning_rate=0.1,\n",
    "        random_state=42\n",
    "    )\n",
    "    xgb_clf.fit(X_train, y_train)\n",
    "    \n",
    "    # 模型3：平衡随机森林（自带采样机制）\n",
    "    brf = BalancedRandomForestClassifier(\n",
    "        n_estimators=100,\n",
    "        sampling_strategy='auto',  # 每个树都平衡采样\n",
    "        random_state=42,\n",
    "        n_jobs=-1\n",
    "    )\n",
    "    brf.fit(X_train, y_train)\n",
    "    \n",
    "    # 评估并返回模型\n",
    "    models = {'随机森林(权重)': rf, 'XGBoost': xgb_clf, '平衡随机森林': brf}\n",
    "    for name, model in models.items():\n",
    "        print(f\"\\n{name} 评估结果：\")\n",
    "        y_pred = model.predict(X_test)\n",
    "        y_prob = model.predict_proba(X_test)[:, 1]\n",
    "        print(\"混淆矩阵：\")\n",
    "        print(confusion_matrix(y_test, y_pred))\n",
    "        print(\"\\n分类报告：\")\n",
    "        print(classification_report(y_test, y_pred))\n",
    "        print(f\"AUC值：{roc_auc_score(y_test, y_prob):.4f}\")\n",
    "    return models\n",
    "\n",
    "# 用采样后的训练集训练模型\n",
    "models = train_balanced_models(\n",
    "    X_train_resampled, y_train_resampled,\n",
    "    X_test_pca, y_test\n",
    ")\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 4. 关键指标可视化\n",
    "# --------------------------\n",
    "def plot_metrics(model, X_test, y_test, model_name):\n",
    "    y_prob = model.predict_proba(X_test)[:, 1]\n",
    "    \n",
    "    # ROC曲线（整体区分能力）\n",
    "    fpr, tpr, _ = roc_curve(y_test, y_prob)\n",
    "    auc = roc_auc_score(y_test, y_prob)\n",
    "    \n",
    "    # PR曲线（聚焦少数类识别）\n",
    "    precision, recall, _ = precision_recall_curve(y_test, y_prob)\n",
    "    \n",
    "    plt.figure(figsize=(12, 5))\n",
    "    # 左：ROC曲线\n",
    "    plt.subplot(1, 2, 1)\n",
    "    plt.plot(fpr, tpr, label=f'AUC = {auc:.4f}')\n",
    "    plt.plot([0, 1], [0, 1], 'k--')\n",
    "    plt.xlabel('假正例率')\n",
    "    plt.ylabel('真正例率')\n",
    "    plt.title(f'{model_name} ROC曲线')\n",
    "    plt.legend()\n",
    "    \n",
    "    # 右：PR曲线\n",
    "    plt.subplot(1, 2, 2)\n",
    "    plt.plot(recall, precision, label='PR曲线')\n",
    "    plt.xlabel('召回率（漏检率）')\n",
    "    plt.ylabel('精确率（误判率）')\n",
    "    plt.title(f'{model_name} PR曲线')\n",
    "    plt.legend()\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "\n",
    "# 可视化效果最优模型（以XGBoost为例）\n",
    "plot_metrics(models['XGBoost'], X_test_pca, y_test, 'XGBoost')\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 5. 预测第六年数据（最终目标）\n",
    "# --------------------------\n",
    "# 假设第六年数据经过相同特征工程处理后为X_year6_pca\n",
    "# 加载第六年数据并预测\n",
    "# X_year6_pca = ...  # 第六年特征工程后的数据\n",
    "# best_model = models['XGBoost']  # 选择最优模型\n",
    "# year6_pred = best_model.predict(X_year6_pca)\n",
    "# year6_prob = best_model.predict_proba(X_year6_pca)[:, 1]\n",
    "# print(\"第六年造假预测概率：\")\n",
    "# print(pd.Series(year6_prob, name='造假概率'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "d35ea683-f0d3-42f0-900f-41c4b470d67b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "使用真实数据进行模型调优...\n",
      "调优训练集形状: (2983, 69)\n",
      "调优测试集形状: (746, 69)\n",
      "调优训练集类别分布:\n",
      "FLAG\n",
      "0.0    0.988937\n",
      "1.0    0.011063\n",
      "Name: proportion, dtype: float64\n",
      "采样后分布：\n",
      "FLAG\n",
      "0.0    2950\n",
      "1.0     881\n",
      "Name: count, dtype: int64\n",
      "Fitting 3 folds for each of 108 candidates, totalling 324 fits\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\joblib\\parallel.py:1650\u001b[0m, in \u001b[0;36mParallel._get_outputs\u001b[1;34m(self, iterator, pre_dispatch)\u001b[0m\n\u001b[0;32m   1649\u001b[0m     \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backend\u001b[38;5;241m.\u001b[39mretrieval_context():\n\u001b[1;32m-> 1650\u001b[0m         \u001b[38;5;28;01myield from\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_retrieve()\n\u001b[0;32m   1652\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mGeneratorExit\u001b[39;00m:\n\u001b[0;32m   1653\u001b[0m     \u001b[38;5;66;03m# The generator has been garbage collected before being fully\u001b[39;00m\n\u001b[0;32m   1654\u001b[0m     \u001b[38;5;66;03m# consumed. This aborts the remaining tasks if possible and warn\u001b[39;00m\n\u001b[0;32m   1655\u001b[0m     \u001b[38;5;66;03m# the user if necessary.\u001b[39;00m\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\joblib\\parallel.py:1762\u001b[0m, in \u001b[0;36mParallel._retrieve\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m   1759\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ((\u001b[38;5;28mlen\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jobs) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m0\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m\n\u001b[0;32m   1760\u001b[0m     (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jobs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mget_status(\n\u001b[0;32m   1761\u001b[0m         timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtimeout) \u001b[38;5;241m==\u001b[39m TASK_PENDING)):\n\u001b[1;32m-> 1762\u001b[0m     \u001b[43mtime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.01\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1763\u001b[0m     \u001b[38;5;28;01mcontinue\u001b[39;00m\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: ",
      "\nDuring handling of the above exception, another exception occurred:\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[22], line 107\u001b[0m\n\u001b[0;32m    104\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m best_model\n\u001b[0;32m    106\u001b[0m \u001b[38;5;66;03m# 训练调优模型\u001b[39;00m\n\u001b[1;32m--> 107\u001b[0m best_rf_tuned \u001b[38;5;241m=\u001b[39m \u001b[43mtrain_tuned_rf\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX_train_resampled\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my_train_resampled\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mX_test_tune\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my_test_tune\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    110\u001b[0m \u001b[38;5;66;03m# --------------------------\u001b[39;00m\n\u001b[0;32m    111\u001b[0m \u001b[38;5;66;03m# 4. 在完整测试集上评估最终模型\u001b[39;00m\n\u001b[0;32m    112\u001b[0m \u001b[38;5;66;03m# --------------------------\u001b[39;00m\n\u001b[0;32m    113\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m最终模型在完整测试集上的表现:\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
      "Cell \u001b[1;32mIn[22], line 86\u001b[0m, in \u001b[0;36mtrain_tuned_rf\u001b[1;34m(X_train, y_train, X_test, y_test)\u001b[0m\n\u001b[0;32m     77\u001b[0m \u001b[38;5;66;03m# 初始化网格搜索（使用F1评分平衡精确率和召回率）\u001b[39;00m\n\u001b[0;32m     78\u001b[0m grid_search \u001b[38;5;241m=\u001b[39m GridSearchCV(\n\u001b[0;32m     79\u001b[0m     estimator\u001b[38;5;241m=\u001b[39mRandomForestClassifier(random_state\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m42\u001b[39m, n_jobs\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m),\n\u001b[0;32m     80\u001b[0m     param_grid\u001b[38;5;241m=\u001b[39mparam_grid,\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m     84\u001b[0m     verbose\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m  \u001b[38;5;66;03m# 输出调优过程（可选）\u001b[39;00m\n\u001b[0;32m     85\u001b[0m )\n\u001b[1;32m---> 86\u001b[0m \u001b[43mgrid_search\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX_train\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my_train\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m     88\u001b[0m \u001b[38;5;66;03m# 最优模型评估\u001b[39;00m\n\u001b[0;32m     89\u001b[0m best_model \u001b[38;5;241m=\u001b[39m grid_search\u001b[38;5;241m.\u001b[39mbest_estimator_\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\sklearn\\base.py:1151\u001b[0m, in \u001b[0;36m_fit_context.<locals>.decorator.<locals>.wrapper\u001b[1;34m(estimator, *args, **kwargs)\u001b[0m\n\u001b[0;32m   1144\u001b[0m     estimator\u001b[38;5;241m.\u001b[39m_validate_params()\n\u001b[0;32m   1146\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m config_context(\n\u001b[0;32m   1147\u001b[0m     skip_parameter_validation\u001b[38;5;241m=\u001b[39m(\n\u001b[0;32m   1148\u001b[0m         prefer_skip_nested_validation \u001b[38;5;129;01mor\u001b[39;00m global_skip_validation\n\u001b[0;32m   1149\u001b[0m     )\n\u001b[0;32m   1150\u001b[0m ):\n\u001b[1;32m-> 1151\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfit_method\u001b[49m\u001b[43m(\u001b[49m\u001b[43mestimator\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\sklearn\\model_selection\\_search.py:898\u001b[0m, in \u001b[0;36mBaseSearchCV.fit\u001b[1;34m(self, X, y, groups, **fit_params)\u001b[0m\n\u001b[0;32m    892\u001b[0m     results \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_format_results(\n\u001b[0;32m    893\u001b[0m         all_candidate_params, n_splits, all_out, all_more_results\n\u001b[0;32m    894\u001b[0m     )\n\u001b[0;32m    896\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m results\n\u001b[1;32m--> 898\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_run_search\u001b[49m\u001b[43m(\u001b[49m\u001b[43mevaluate_candidates\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    900\u001b[0m \u001b[38;5;66;03m# multimetric is determined here because in the case of a callable\u001b[39;00m\n\u001b[0;32m    901\u001b[0m \u001b[38;5;66;03m# self.scoring the return type is only known after calling\u001b[39;00m\n\u001b[0;32m    902\u001b[0m first_test_score \u001b[38;5;241m=\u001b[39m all_out[\u001b[38;5;241m0\u001b[39m][\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtest_scores\u001b[39m\u001b[38;5;124m\"\u001b[39m]\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\sklearn\\model_selection\\_search.py:1419\u001b[0m, in \u001b[0;36mGridSearchCV._run_search\u001b[1;34m(self, evaluate_candidates)\u001b[0m\n\u001b[0;32m   1417\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_run_search\u001b[39m(\u001b[38;5;28mself\u001b[39m, evaluate_candidates):\n\u001b[0;32m   1418\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m\"\"\"Search all candidates in param_grid\"\"\"\u001b[39;00m\n\u001b[1;32m-> 1419\u001b[0m     \u001b[43mevaluate_candidates\u001b[49m\u001b[43m(\u001b[49m\u001b[43mParameterGrid\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparam_grid\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\sklearn\\model_selection\\_search.py:845\u001b[0m, in \u001b[0;36mBaseSearchCV.fit.<locals>.evaluate_candidates\u001b[1;34m(candidate_params, cv, more_results)\u001b[0m\n\u001b[0;32m    837\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mverbose \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[0;32m    838\u001b[0m     \u001b[38;5;28mprint\u001b[39m(\n\u001b[0;32m    839\u001b[0m         \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFitting \u001b[39m\u001b[38;5;132;01m{0}\u001b[39;00m\u001b[38;5;124m folds for each of \u001b[39m\u001b[38;5;132;01m{1}\u001b[39;00m\u001b[38;5;124m candidates,\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    840\u001b[0m         \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m totalling \u001b[39m\u001b[38;5;132;01m{2}\u001b[39;00m\u001b[38;5;124m fits\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(\n\u001b[0;32m    841\u001b[0m             n_splits, n_candidates, n_candidates \u001b[38;5;241m*\u001b[39m n_splits\n\u001b[0;32m    842\u001b[0m         )\n\u001b[0;32m    843\u001b[0m     )\n\u001b[1;32m--> 845\u001b[0m out \u001b[38;5;241m=\u001b[39m \u001b[43mparallel\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    846\u001b[0m \u001b[43m    \u001b[49m\u001b[43mdelayed\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_fit_and_score\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    847\u001b[0m \u001b[43m        \u001b[49m\u001b[43mclone\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbase_estimator\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    848\u001b[0m \u001b[43m        \u001b[49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    849\u001b[0m \u001b[43m        \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    850\u001b[0m \u001b[43m        \u001b[49m\u001b[43mtrain\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtrain\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    851\u001b[0m \u001b[43m        \u001b[49m\u001b[43mtest\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtest\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    852\u001b[0m \u001b[43m        \u001b[49m\u001b[43mparameters\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mparameters\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    853\u001b[0m \u001b[43m        \u001b[49m\u001b[43msplit_progress\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43msplit_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_splits\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    854\u001b[0m \u001b[43m        \u001b[49m\u001b[43mcandidate_progress\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mcand_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_candidates\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    855\u001b[0m \u001b[43m        \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mfit_and_score_kwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    856\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    857\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[43mcand_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mparameters\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[43msplit_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[43mtrain\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtest\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mproduct\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    858\u001b[0m \u001b[43m        \u001b[49m\u001b[38;5;28;43menumerate\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mcandidate_params\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43menumerate\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mcv\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msplit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgroups\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    859\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    860\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    862\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(out) \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[0;32m    863\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[0;32m    864\u001b[0m         \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo fits were performed. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    865\u001b[0m         \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mWas the CV iterator empty? \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    866\u001b[0m         \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mWere there no candidates?\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    867\u001b[0m     )\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\sklearn\\utils\\parallel.py:65\u001b[0m, in \u001b[0;36mParallel.__call__\u001b[1;34m(self, iterable)\u001b[0m\n\u001b[0;32m     60\u001b[0m config \u001b[38;5;241m=\u001b[39m get_config()\n\u001b[0;32m     61\u001b[0m iterable_with_config \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m     62\u001b[0m     (_with_config(delayed_func, config), args, kwargs)\n\u001b[0;32m     63\u001b[0m     \u001b[38;5;28;01mfor\u001b[39;00m delayed_func, args, kwargs \u001b[38;5;129;01min\u001b[39;00m iterable\n\u001b[0;32m     64\u001b[0m )\n\u001b[1;32m---> 65\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;21;43m__call__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43miterable_with_config\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\joblib\\parallel.py:2007\u001b[0m, in \u001b[0;36mParallel.__call__\u001b[1;34m(self, iterable)\u001b[0m\n\u001b[0;32m   2001\u001b[0m \u001b[38;5;66;03m# The first item from the output is blank, but it makes the interpreter\u001b[39;00m\n\u001b[0;32m   2002\u001b[0m \u001b[38;5;66;03m# progress until it enters the Try/Except block of the generator and\u001b[39;00m\n\u001b[0;32m   2003\u001b[0m \u001b[38;5;66;03m# reaches the first `yield` statement. This starts the asynchronous\u001b[39;00m\n\u001b[0;32m   2004\u001b[0m \u001b[38;5;66;03m# dispatch of the tasks to the workers.\u001b[39;00m\n\u001b[0;32m   2005\u001b[0m \u001b[38;5;28mnext\u001b[39m(output)\n\u001b[1;32m-> 2007\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m output \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mreturn_generator \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43moutput\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\joblib\\parallel.py:1703\u001b[0m, in \u001b[0;36mParallel._get_outputs\u001b[1;34m(self, iterator, pre_dispatch)\u001b[0m\n\u001b[0;32m   1701\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m:\n\u001b[0;32m   1702\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_exception \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[1;32m-> 1703\u001b[0m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_abort\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1704\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m\n\u001b[0;32m   1705\u001b[0m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[0;32m   1706\u001b[0m     \u001b[38;5;66;03m# Store the unconsumed tasks and terminate the workers if necessary\u001b[39;00m\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\joblib\\parallel.py:1614\u001b[0m, in \u001b[0;36mParallel._abort\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m   1609\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m (\u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_aborted \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(backend, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mabort_everything\u001b[39m\u001b[38;5;124m'\u001b[39m)):\n\u001b[0;32m   1610\u001b[0m     \u001b[38;5;66;03m# If the backend is managed externally we need to make sure\u001b[39;00m\n\u001b[0;32m   1611\u001b[0m     \u001b[38;5;66;03m# to leave it in a working state to allow for future jobs\u001b[39;00m\n\u001b[0;32m   1612\u001b[0m     \u001b[38;5;66;03m# scheduling.\u001b[39;00m\n\u001b[0;32m   1613\u001b[0m     ensure_ready \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_managed_backend\n\u001b[1;32m-> 1614\u001b[0m     \u001b[43mbackend\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mabort_everything\u001b[49m\u001b[43m(\u001b[49m\u001b[43mensure_ready\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mensure_ready\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1615\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_aborted \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\joblib\\_parallel_backends.py:620\u001b[0m, in \u001b[0;36mLokyBackend.abort_everything\u001b[1;34m(self, ensure_ready)\u001b[0m\n\u001b[0;32m    617\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mabort_everything\u001b[39m(\u001b[38;5;28mself\u001b[39m, ensure_ready\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[0;32m    618\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m\"\"\"Shutdown the workers and restart a new one with the same parameters\u001b[39;00m\n\u001b[0;32m    619\u001b[0m \u001b[38;5;124;03m    \"\"\"\u001b[39;00m\n\u001b[1;32m--> 620\u001b[0m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_workers\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mterminate\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkill_workers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[0;32m    621\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_workers \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m    623\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m ensure_ready:\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\joblib\\executor.py:75\u001b[0m, in \u001b[0;36mMemmappingExecutor.terminate\u001b[1;34m(self, kill_workers)\u001b[0m\n\u001b[0;32m     73\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mterminate\u001b[39m(\u001b[38;5;28mself\u001b[39m, kill_workers\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m):\n\u001b[1;32m---> 75\u001b[0m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mshutdown\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkill_workers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mkill_workers\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m     77\u001b[0m     \u001b[38;5;66;03m# When workers are killed in a brutal manner, they cannot execute the\u001b[39;00m\n\u001b[0;32m     78\u001b[0m     \u001b[38;5;66;03m# finalizer of their shared memmaps. The refcount of those memmaps may\u001b[39;00m\n\u001b[0;32m     79\u001b[0m     \u001b[38;5;66;03m# be off by an unknown number, so instead of decref'ing them, we force\u001b[39;00m\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m     84\u001b[0m     \u001b[38;5;66;03m# with allow_non_empty=True but if we can't, it will be clean up later\u001b[39;00m\n\u001b[0;32m     85\u001b[0m     \u001b[38;5;66;03m# on by the resource_tracker.\u001b[39;00m\n\u001b[0;32m     86\u001b[0m     \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_submit_resize_lock:\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\joblib\\externals\\loky\\process_executor.py:1303\u001b[0m, in \u001b[0;36mProcessPoolExecutor.shutdown\u001b[1;34m(self, wait, kill_workers)\u001b[0m\n\u001b[0;32m   1299\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m executor_manager_thread \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m wait:\n\u001b[0;32m   1300\u001b[0m     \u001b[38;5;66;03m# This locks avoids concurrent join if the interpreter\u001b[39;00m\n\u001b[0;32m   1301\u001b[0m     \u001b[38;5;66;03m# is shutting down.\u001b[39;00m\n\u001b[0;32m   1302\u001b[0m     \u001b[38;5;28;01mwith\u001b[39;00m _global_shutdown_lock:\n\u001b[1;32m-> 1303\u001b[0m         \u001b[43mexecutor_manager_thread\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mjoin\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1304\u001b[0m         _threads_wakeups\u001b[38;5;241m.\u001b[39mpop(executor_manager_thread, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[0;32m   1306\u001b[0m \u001b[38;5;66;03m# To reduce the risk of opening too many files, remove references to\u001b[39;00m\n\u001b[0;32m   1307\u001b[0m \u001b[38;5;66;03m# objects that use file descriptors.\u001b[39;00m\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\threading.py:1011\u001b[0m, in \u001b[0;36mThread.join\u001b[1;34m(self, timeout)\u001b[0m\n\u001b[0;32m   1008\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcannot join current thread\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m   1010\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m timeout \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m-> 1011\u001b[0m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_wait_for_tstate_lock\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1012\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m   1013\u001b[0m     \u001b[38;5;66;03m# the behavior of a negative timeout isn't documented, but\u001b[39;00m\n\u001b[0;32m   1014\u001b[0m     \u001b[38;5;66;03m# historically .join(timeout=x) for x<0 has acted as if timeout=0\u001b[39;00m\n\u001b[0;32m   1015\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_wait_for_tstate_lock(timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mmax\u001b[39m(timeout, \u001b[38;5;241m0\u001b[39m))\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\threading.py:1027\u001b[0m, in \u001b[0;36mThread._wait_for_tstate_lock\u001b[1;34m(self, block, timeout)\u001b[0m\n\u001b[0;32m   1025\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m lock \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:  \u001b[38;5;66;03m# already determined that the C code is done\u001b[39;00m\n\u001b[0;32m   1026\u001b[0m     \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_is_stopped\n\u001b[1;32m-> 1027\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[43mlock\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43macquire\u001b[49m\u001b[43m(\u001b[49m\u001b[43mblock\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m:\n\u001b[0;32m   1028\u001b[0m     lock\u001b[38;5;241m.\u001b[39mrelease()\n\u001b[0;32m   1029\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_stop()\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.model_selection import GridSearchCV, train_test_split\n",
    "from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix  # 补充缺失的导入\n",
    "from imblearn.over_sampling import ADASYN\n",
    "import joblib\n",
    "\n",
    "# --------------------------\n",
    "# 1. 数据准备（使用之前特征工程得到的数据）\n",
    "# --------------------------\n",
    "# 使用之前特征工程得到的 X_train_final, X_test_final, y_train, y_test\n",
    "print(\"使用真实数据进行模型调优...\")\n",
    "\n",
    "# 检查输入数据有效性（新增：避免后续步骤因数据格式错误崩溃）\n",
    "if not all([isinstance(x, pd.DataFrame) for x in [X_train_final, X_test_final]]):\n",
    "    raise TypeError(\"X_train_final和X_test_final必须为DataFrame格式\")\n",
    "if not all([isinstance(y, (pd.Series, np.ndarray)) for y in [y_train, y_test]]):\n",
    "    raise TypeError(\"y_train和y_test必须为Series或ndarray格式\")\n",
    "\n",
    "# 重新划分训练集和测试集（保持类别比例）\n",
    "X_train_tune, X_test_tune, y_train_tune, y_test_tune = train_test_split(\n",
    "    X_train_final, y_train, \n",
    "    test_size=0.2, stratify=y_train, random_state=42\n",
    ")\n",
    "\n",
    "print(f\"调优训练集形状: {X_train_tune.shape}\")\n",
    "print(f\"调优测试集形状: {X_test_tune.shape}\")\n",
    "print(\"调优训练集类别分布:\")\n",
    "print(pd.Series(y_train_tune).value_counts(normalize=True))\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 2. 极端不均衡处理\n",
    "# --------------------------\n",
    "def handle_imbalance(X, y):\n",
    "    \"\"\"过采样处理不均衡数据，增加异常处理\"\"\"\n",
    "    # 检查输入是否为空\n",
    "    if X.shape[0] == 0 or len(y) == 0:\n",
    "        raise ValueError(\"输入数据为空，无法进行采样\")\n",
    "    # 检查类别数量（至少需要2个类别）\n",
    "    if len(np.unique(y)) < 2:\n",
    "        raise ValueError(\"目标变量至少需要包含2个类别\")\n",
    "    \n",
    "    sampler = ADASYN(\n",
    "        sampling_strategy=0.3,  # 少数类:多数类 = 3:10\n",
    "        random_state=42,\n",
    "        n_neighbors=5\n",
    "    )\n",
    "    X_resampled, y_resampled = sampler.fit_resample(X, y)\n",
    "    print(f\"采样后分布：\\n{pd.Series(y_resampled).value_counts()}\")\n",
    "    return X_resampled, y_resampled\n",
    "\n",
    "# 调用采样函数（增加异常捕获）\n",
    "try:\n",
    "    X_train_resampled, y_train_resampled = handle_imbalance(X_train_tune, y_train_tune)\n",
    "except Exception as e:\n",
    "    print(f\"采样失败：{str(e)}\")\n",
    "    # 若采样失败，使用原始数据（降级策略）\n",
    "    X_train_resampled, y_train_resampled = X_train_tune, y_train_tune\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 3. 模型训练 + 超参数调优（聚焦少数类）\n",
    "# --------------------------\n",
    "def train_tuned_rf(X_train, y_train, X_test, y_test):\n",
    "    \"\"\"训练调优后的随机森林，优化参数格式和评估稳健性\"\"\"\n",
    "    # 修正参数网格错误：class_weight中字典key不能重复（原代码{0:1, 1:30, 1:50}存在重复key）\n",
    "    param_grid = {\n",
    "        \"n_estimators\": [100, 200],  # 树的数量\n",
    "        \"max_depth\": [None, 8, 15],  # 树深度（控制过拟合）\n",
    "        \"min_samples_leaf\": [1, 3, 5],  # 叶节点最小样本数（避免过拟合）\n",
    "        \"class_weight\": [\"balanced\", {0:1, 1:30}, {0:1, 1:50}],  # 修正为两个独立字典\n",
    "        \"criterion\": [\"gini\", \"entropy\"]  # 分裂标准\n",
    "    }\n",
    "    \n",
    "    # 初始化网格搜索（使用F1评分平衡精确率和召回率）\n",
    "    grid_search = GridSearchCV(\n",
    "        estimator=RandomForestClassifier(random_state=42, n_jobs=-1),\n",
    "        param_grid=param_grid,\n",
    "        cv=3,  # 3折交叉验证\n",
    "        scoring=\"f1\",  # 适合不平衡数据的评分\n",
    "        n_jobs=-1,  # 并行计算\n",
    "        verbose=1  # 输出调优过程（可选）\n",
    "    )\n",
    "    grid_search.fit(X_train, y_train)\n",
    "    \n",
    "    # 最优模型评估\n",
    "    best_model = grid_search.best_estimator_\n",
    "    y_pred = best_model.predict(X_test)\n",
    "    y_prob = best_model.predict_proba(X_test)[:, 1]\n",
    "    \n",
    "    # 稳健性处理：避免类别1不存在导致的KeyError\n",
    "    try:\n",
    "        f1_score = classification_report(y_test, y_pred, output_dict=True)['1']['f1-score']\n",
    "    except KeyError:\n",
    "        # 若类别1不存在，使用宏平均F1\n",
    "        f1_score = classification_report(y_test, y_pred, output_dict=True)['macro avg']['f1-score']\n",
    "    \n",
    "    print(f\"最优参数：{grid_search.best_params_}\")\n",
    "    print(f\"F1分数（测试集）：{f1_score:.2f}\")\n",
    "    print(f\"AUC值：{roc_auc_score(y_test, y_prob):.2f}\")\n",
    "    \n",
    "    return best_model\n",
    "\n",
    "# 训练调优模型\n",
    "best_rf_tuned = train_tuned_rf(X_train_resampled, y_train_resampled, X_test_tune, y_test_tune)\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 4. 在完整测试集上评估最终模型\n",
    "# --------------------------\n",
    "print(\"\\n最终模型在完整测试集上的表现:\")\n",
    "y_test_pred = best_rf_tuned.predict(X_test_final)\n",
    "y_test_prob = best_rf_tuned.predict_proba(X_test_final)[:, 1]\n",
    "\n",
    "print(\"混淆矩阵:\")\n",
    "print(confusion_matrix(y_test, y_test_pred))\n",
    "print(\"\\n分类报告:\")\n",
    "print(classification_report(y_test, y_test_pred))\n",
    "print(f\"AUC值：{roc_auc_score(y_test, y_test_prob):.4f}\")\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 5. 模型保存（增强完整性）\n",
    "# --------------------------\n",
    "# 保存最终模型\n",
    "joblib.dump(best_rf_tuned, \"fraud_detection_final_model.pkl\")\n",
    "# 保存特征名称（确保预测时特征对齐）\n",
    "joblib.dump(feature_names, \"feature_names.pkl\")\n",
    "# 新增：保存调优后的参数（便于后续复现）\n",
    "with open(\"best_model_params.txt\", \"w\") as f:\n",
    "    f.write(str(best_rf_tuned.get_params()))\n",
    "\n",
    "print(\"模型、特征名称和最优参数已保存\")\n",
    "print(f\"最终模型使用的特征数量: {len(feature_names)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "85ddcd8c-9c94-465c-98e0-63ac6a4a3320",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true,
     "source_hidden": true
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "使用真实数据进行模型调优...\n",
      "调优训练集形状: (2983, 73)\n",
      "调优测试集形状: (746, 73)\n",
      "调优训练集类别分布:\n",
      "FLAG\n",
      "0.0    0.988937\n",
      "1.0    0.011063\n",
      "Name: proportion, dtype: float64\n",
      "采样后分布：\n",
      "FLAG\n",
      "0.0    2950\n",
      "1.0     871\n",
      "Name: count, dtype: int64\n",
      "最优参数：{'class_weight': {0: 1, 1: 50}, 'criterion': 'gini', 'max_depth': 8, 'min_samples_leaf': 5, 'n_estimators': 100}\n"
     ]
    },
    {
     "ename": "KeyError",
     "evalue": "'1'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[28], line 80\u001b[0m\n\u001b[0;32m     77\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m best_model\n\u001b[0;32m     79\u001b[0m \u001b[38;5;66;03m# 训练并评估调优后的模型\u001b[39;00m\n\u001b[1;32m---> 80\u001b[0m best_rf_tuned \u001b[38;5;241m=\u001b[39m \u001b[43mtrain_tuned_rf\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX_train_resampled\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my_train_resampled\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mX_test_tune\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my_test_tune\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m     83\u001b[0m \u001b[38;5;66;03m# --------------------------\u001b[39;00m\n\u001b[0;32m     84\u001b[0m \u001b[38;5;66;03m# 4. 在完整测试集上评估最终模型\u001b[39;00m\n\u001b[0;32m     85\u001b[0m \u001b[38;5;66;03m# --------------------------\u001b[39;00m\n\u001b[0;32m     86\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m最终模型在完整测试集上的表现:\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
      "Cell \u001b[1;32mIn[28], line 74\u001b[0m, in \u001b[0;36mtrain_tuned_rf\u001b[1;34m(X_train, y_train, X_test, y_test)\u001b[0m\n\u001b[0;32m     71\u001b[0m y_prob \u001b[38;5;241m=\u001b[39m best_model\u001b[38;5;241m.\u001b[39mpredict_proba(X_test)[:, \u001b[38;5;241m1\u001b[39m]\n\u001b[0;32m     73\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m最优参数：\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mgrid_search\u001b[38;5;241m.\u001b[39mbest_params_\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m---> 74\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m召回率（测试集）：\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mclassification_report(y_test,\u001b[38;5;250m \u001b[39my_pred,\u001b[38;5;250m \u001b[39moutput_dict\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m1\u001b[39m\u001b[38;5;124m'\u001b[39m][\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mrecall\u001b[39m\u001b[38;5;124m'\u001b[39m]\u001b[38;5;132;01m:\u001b[39;00m\u001b[38;5;124m.2f\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m     75\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAUC值：\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mroc_auc_score(y_test,\u001b[38;5;250m \u001b[39my_prob)\u001b[38;5;132;01m:\u001b[39;00m\u001b[38;5;124m.2f\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m     77\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m best_model\n",
      "\u001b[1;31mKeyError\u001b[0m: '1'"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.model_selection import GridSearchCV, train_test_split\n",
    "from sklearn.metrics import classification_report, roc_auc_score\n",
    "from imblearn.over_sampling import ADASYN\n",
    "import joblib\n",
    "\n",
    "# --------------------------\n",
    "# 1. 数据准备（使用之前特征工程得到的数据）\n",
    "# --------------------------\n",
    "# 使用之前特征工程得到的 X_train_final, X_test_final, y_train, y_test\n",
    "print(\"使用真实数据进行模型调优...\")\n",
    "\n",
    "# 重新划分训练集和测试集（保持类别比例）\n",
    "X_train_tune, X_test_tune, y_train_tune, y_test_tune = train_test_split(\n",
    "    X_train_final, y_train, \n",
    "    test_size=0.2, stratify=y_train, random_state=42\n",
    ")\n",
    "\n",
    "print(f\"调优训练集形状: {X_train_tune.shape}\")\n",
    "print(f\"调优测试集形状: {X_test_tune.shape}\")\n",
    "print(\"调优训练集类别分布:\")\n",
    "print(pd.Series(y_train_tune).value_counts(normalize=True))\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 2. 极端不均衡处理\n",
    "# --------------------------\n",
    "def handle_imbalance(X, y):\n",
    "    # 用ADASYN过采样\n",
    "    sampler = ADASYN(\n",
    "        sampling_strategy=0.3,\n",
    "        random_state=42,\n",
    "        n_neighbors=5\n",
    "    )\n",
    "    X_resampled, y_resampled = sampler.fit_resample(X, y)\n",
    "    print(f\"采样后分布：\\n{pd.Series(y_resampled).value_counts()}\")\n",
    "    return X_resampled, y_resampled\n",
    "\n",
    "X_train_resampled, y_train_resampled = handle_imbalance(X_train_tune, y_train_tune)\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 3. 模型训练 + 超参数调优（聚焦少数类）\n",
    "# --------------------------\n",
    "def train_tuned_rf(X_train, y_train, X_test, y_test):\n",
    "    # 定义针对性的参数网格\n",
    "    param_grid = {\n",
    "        \"n_estimators\": [100, 200],\n",
    "        \"max_depth\": [None, 8, 15],\n",
    "        \"min_samples_leaf\": [1, 3, 5],\n",
    "        \"class_weight\": [\"balanced\", {0:1, 1:50}],\n",
    "        \"criterion\": [\"gini\", \"entropy\"]\n",
    "    }\n",
    "    \n",
    "    # 初始化模型 + 网格搜索\n",
    "    rf = RandomForestClassifier(random_state=42, n_jobs=-1)\n",
    "    grid_search = GridSearchCV(\n",
    "        estimator=rf,\n",
    "        param_grid=param_grid,\n",
    "        cv=3,\n",
    "        scoring=\"recall\",\n",
    "        n_jobs=-1\n",
    "    )\n",
    "    grid_search.fit(X_train, y_train)\n",
    "    \n",
    "    # 最优模型评估\n",
    "    best_model = grid_search.best_estimator_\n",
    "    y_pred = best_model.predict(X_test)\n",
    "    y_prob = best_model.predict_proba(X_test)[:, 1]\n",
    "    \n",
    "    print(f\"最优参数：{grid_search.best_params_}\")\n",
    "    print(f\"召回率（测试集）：{classification_report(y_test, y_pred, output_dict=True)['1']['recall']:.2f}\")\n",
    "    print(f\"AUC值：{roc_auc_score(y_test, y_prob):.2f}\")\n",
    "    \n",
    "    return best_model\n",
    "\n",
    "# 训练并评估调优后的模型\n",
    "best_rf_tuned = train_tuned_rf(X_train_resampled, y_train_resampled, X_test_tune, y_test_tune)\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 4. 在完整测试集上评估最终模型\n",
    "# --------------------------\n",
    "print(\"\\n最终模型在完整测试集上的表现:\")\n",
    "y_test_pred = best_rf_tuned.predict(X_test_final)\n",
    "y_test_prob = best_rf_tuned.predict_proba(X_test_final)[:, 1]\n",
    "\n",
    "print(\"混淆矩阵:\")\n",
    "print(confusion_matrix(y_test, y_test_pred))\n",
    "print(\"\\n分类报告:\")\n",
    "print(classification_report(y_test, y_test_pred))\n",
    "print(f\"AUC值：{roc_auc_score(y_test, y_test_prob):.4f}\")\n",
    "\n",
    "\n",
    "# --------------------------\n",
    "# 5. 模型保存\n",
    "# --------------------------\n",
    "# 保存最终模型\n",
    "#joblib.dump(best_rf_tuned, \"fraud_detection_final_model.pkl\")\n",
    "# 保存特征名称\n",
    "#joblib.dump(feature_names, \"feature_names.pkl\")\n",
    "\n",
    "print(\"模型和特征名称已保存\")\n",
    "print(f\"最终模型使用的特征数量: {len(feature_names)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "936c15b8-92f4-4f9e-be39-d500b396c06c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# --------------------------\n",
    "# 第六年数据预测\n",
    "# --------------------------\n",
    "def preprocess_new_data(df, preprocessing_tools):\n",
    "    \"\"\"用训练集的预处理规则处理新数据（第六年）\"\"\"\n",
    "    # 加载预处理工具\n",
    "    scaler = preprocessing_tools['scaler']\n",
    "    selector = preprocessing_tools['selector']\n",
    "    train_means = preprocessing_tools['train_means']\n",
    "    train_stds = preprocessing_tools['train_stds']\n",
    "    train_vif_columns = preprocessing_tools['train_vif_columns']\n",
    "    feature_names = preprocessing_tools['feature_names']\n",
    "    derived_medians = preprocessing_tools['derived_cols_medians']\n",
    "    \n",
    "    # 1. 提取特征（排除标签列，假设最后一列为标签）\n",
    "    df_features = df.iloc[:, :-1].copy()\n",
    "    \n",
    "    # 2. 标准化（用训练集scaler）\n",
    "    df_scaled = scaler.transform(df_features)\n",
    "    df_scaled = pd.DataFrame(df_scaled, columns=df_features.columns, index=df_features.index)\n",
    "    \n",
    "    # 3. 异常值处理（用训练集均值/标准差）\n",
    "    df_clean = handle_outliers(df_scaled, train_means, train_stds)\n",
    "    \n",
    "    # 4. 特征选择（与训练集一致）\n",
    "    df_selected = selector.transform(df_clean)\n",
    "    df_selected = pd.DataFrame(df_selected, columns=selected_cols, index=df_clean.index)\n",
    "    \n",
    "    # 5. 去除高VIF特征\n",
    "    df_vif = df_selected[train_vif_columns]\n",
    "    \n",
    "    # 6. 构造衍生特征\n",
    "    df_derived = create_derived_features(df_vif)\n",
    "    \n",
    "    # 7. 衍生特征缺失值填充（用训练集中位数）\n",
    "    for col in derived_medians:\n",
    "        if col in df_derived.columns:\n",
    "            df_derived[col] = df_derived[col].replace([np.inf, -np.inf], np.nan).fillna(derived_medians[col])\n",
    "    \n",
    "    # 8. 特征对齐（确保与训练集特征一致）\n",
    "    missing_cols = set(feature_names) - set(df_derived.columns)\n",
    "    for col in missing_cols:\n",
    "        df_derived[col] = 0  # 缺失特征补0\n",
    "    df_final = df_derived[feature_names]\n",
    "    \n",
    "    return df_final\n",
    "\n",
    "# 加载工具和模型\n",
    "preprocessing_tools = joblib.load(\"preprocessing_tools.pkl\")\n",
    "best_model = joblib.load(\"fraud_detection_final_model.pkl\")\n",
    "\n",
    "# 处理第六年数据并预测\n",
    "datas_y6_processed = preprocess_new_data(datas_manual_y6, preprocessing_tools)\n",
    "y6_pred_prob = best_model.predict_proba(datas_y6_processed)[:, 1]\n",
    "y6_pred = best_model.predict(datas_y6_processed)\n",
    "\n",
    "# 输出预测结果\n",
    "result = pd.DataFrame({\n",
    "    'TICKER_SYMBOL': datas_manual_y6['TICKER_SYMBOL'].values,\n",
    "    '预测概率': y6_pred_prob,\n",
    "    '预测标签': y6_pred\n",
    "})\n",
    "print(\"第六年预测结果：\")\n",
    "print(result.head())\n",
    "result.to_csv(\"第六年财务造假预测结果.csv\", index=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f3c47869-9cd9-445a-ba5a-b850fe2ac59a",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "87d3e9c8-40fa-429f-b2f9-e1aa5ce8c09d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "加载数据...\n"
     ]
    },
    {
     "ename": "KeyError",
     "evalue": "\"['LABEL'] not found in axis\"",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[24], line 152\u001b[0m\n\u001b[0;32m    149\u001b[0m     \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m模型已保存\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m    151\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;18m__name__\u001b[39m \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m__main__\u001b[39m\u001b[38;5;124m'\u001b[39m:\n\u001b[1;32m--> 152\u001b[0m     \u001b[43mmain\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
      "Cell \u001b[1;32mIn[24], line 134\u001b[0m, in \u001b[0;36mmain\u001b[1;34m()\u001b[0m\n\u001b[0;32m    131\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m加载数据...\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m    132\u001b[0m data_y5, data_y6 \u001b[38;5;241m=\u001b[39m load_and_preprocess()\n\u001b[1;32m--> 134\u001b[0m X \u001b[38;5;241m=\u001b[39m \u001b[43mdata_y5\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdrop\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcolumns\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mLABEL\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m  \u001b[38;5;66;03m# 假设最后一列是标签\u001b[39;00m\n\u001b[0;32m    135\u001b[0m y \u001b[38;5;241m=\u001b[39m data_y5[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mLABEL\u001b[39m\u001b[38;5;124m'\u001b[39m]\n\u001b[0;32m    137\u001b[0m X_train, X_test, y_train, y_test \u001b[38;5;241m=\u001b[39m train_test_split(X, y, test_size\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m0.3\u001b[39m, random_state\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m42\u001b[39m, stratify\u001b[38;5;241m=\u001b[39my)\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\pandas\\core\\frame.py:5258\u001b[0m, in \u001b[0;36mDataFrame.drop\u001b[1;34m(self, labels, axis, index, columns, level, inplace, errors)\u001b[0m\n\u001b[0;32m   5110\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mdrop\u001b[39m(\n\u001b[0;32m   5111\u001b[0m     \u001b[38;5;28mself\u001b[39m,\n\u001b[0;32m   5112\u001b[0m     labels: IndexLabel \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m   5119\u001b[0m     errors: IgnoreRaise \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mraise\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m   5120\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m DataFrame \u001b[38;5;241m|\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m   5121\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m   5122\u001b[0m \u001b[38;5;124;03m    Drop specified labels from rows or columns.\u001b[39;00m\n\u001b[0;32m   5123\u001b[0m \n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m   5256\u001b[0m \u001b[38;5;124;03m            weight  1.0     0.8\u001b[39;00m\n\u001b[0;32m   5257\u001b[0m \u001b[38;5;124;03m    \"\"\"\u001b[39;00m\n\u001b[1;32m-> 5258\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdrop\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   5259\u001b[0m \u001b[43m        \u001b[49m\u001b[43mlabels\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mlabels\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   5260\u001b[0m \u001b[43m        \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43maxis\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   5261\u001b[0m \u001b[43m        \u001b[49m\u001b[43mindex\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   5262\u001b[0m \u001b[43m        \u001b[49m\u001b[43mcolumns\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcolumns\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   5263\u001b[0m \u001b[43m        \u001b[49m\u001b[43mlevel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mlevel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   5264\u001b[0m \u001b[43m        \u001b[49m\u001b[43minplace\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minplace\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   5265\u001b[0m \u001b[43m        \u001b[49m\u001b[43merrors\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43merrors\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   5266\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\pandas\\core\\generic.py:4549\u001b[0m, in \u001b[0;36mNDFrame.drop\u001b[1;34m(self, labels, axis, index, columns, level, inplace, errors)\u001b[0m\n\u001b[0;32m   4547\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m axis, labels \u001b[38;5;129;01min\u001b[39;00m axes\u001b[38;5;241m.\u001b[39mitems():\n\u001b[0;32m   4548\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m labels \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m-> 4549\u001b[0m         obj \u001b[38;5;241m=\u001b[39m \u001b[43mobj\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_drop_axis\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlabels\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlevel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mlevel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43merrors\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43merrors\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   4551\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m inplace:\n\u001b[0;32m   4552\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_update_inplace(obj)\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\pandas\\core\\generic.py:4591\u001b[0m, in \u001b[0;36mNDFrame._drop_axis\u001b[1;34m(self, labels, axis, level, errors, only_slice)\u001b[0m\n\u001b[0;32m   4589\u001b[0m         new_axis \u001b[38;5;241m=\u001b[39m axis\u001b[38;5;241m.\u001b[39mdrop(labels, level\u001b[38;5;241m=\u001b[39mlevel, errors\u001b[38;5;241m=\u001b[39merrors)\n\u001b[0;32m   4590\u001b[0m     \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m-> 4591\u001b[0m         new_axis \u001b[38;5;241m=\u001b[39m \u001b[43maxis\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdrop\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlabels\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43merrors\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43merrors\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   4592\u001b[0m     indexer \u001b[38;5;241m=\u001b[39m axis\u001b[38;5;241m.\u001b[39mget_indexer(new_axis)\n\u001b[0;32m   4594\u001b[0m \u001b[38;5;66;03m# Case for non-unique axis\u001b[39;00m\n\u001b[0;32m   4595\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n",
      "File \u001b[1;32m~\\anaconda3\\lib\\site-packages\\pandas\\core\\indexes\\base.py:6699\u001b[0m, in \u001b[0;36mIndex.drop\u001b[1;34m(self, labels, errors)\u001b[0m\n\u001b[0;32m   6697\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m mask\u001b[38;5;241m.\u001b[39many():\n\u001b[0;32m   6698\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m errors \u001b[38;5;241m!=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mignore\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m-> 6699\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlist\u001b[39m(labels[mask])\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m not found in axis\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m   6700\u001b[0m     indexer \u001b[38;5;241m=\u001b[39m indexer[\u001b[38;5;241m~\u001b[39mmask]\n\u001b[0;32m   6701\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdelete(indexer)\n",
      "\u001b[1;31mKeyError\u001b[0m: \"['LABEL'] not found in axis\""
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "import joblib\n",
    "from sklearn.model_selection import train_test_split, GridSearchCV\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.feature_selection import mutual_info_classif, SelectKBest\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix, roc_curve, precision_recall_curve\n",
    "from sklearn.decomposition import PCA\n",
    "# 修复导入问题\n",
    "from imblearn.combine import SMOTETomek  # SMOTETomek 在 combine 模块中\n",
    "from imblearn.over_sampling import ADASYN\n",
    "from imblearn.ensemble import BalancedRandomForestClassifier\n",
    "from statsmodels.stats.outliers_influence import variance_inflation_factor\n",
    "\n",
    "\n",
    "# 设置中文字体\n",
    "plt.rcParams['font.family'] = 'SimSun'\n",
    "plt.rcParams['axes.unicode_minus'] = False\n",
    "\n",
    "# --------------------------\n",
    "# 1. 数据加载与预处理\n",
    "# --------------------------\n",
    "def load_and_preprocess():\n",
    "    data = pd.read_csv(r'C:\\Users\\mwj\\Desktop\\01-财务造假\\5. 各公司财务报告.csv')\n",
    "    industry = pd.read_excel(r'C:\\Users\\mwj\\Desktop\\01-财务造假\\5. 公司对应行业.xlsx')\n",
    "    industry.rename(columns={'股票代码': 'TICKER_SYMBOL'}, inplace=True)\n",
    "    industry['TICKER_SYMBOL'] = industry['TICKER_SYMBOL'].astype(int)\n",
    "    data_merge = pd.merge(data, industry, how='left', on=['TICKER_SYMBOL'])\n",
    "    datas_manual = data_merge[data_merge['所属行业'] == '制造业']\n",
    "    datas_manual_y5 = datas_manual[datas_manual['END_DATE'] != 6].copy()\n",
    "    datas_manual_y6 = datas_manual[datas_manual['END_DATE'] == 6].copy()\n",
    "\n",
    "    # 去除单一值特征\n",
    "    single_value_columns = [col for col in datas_manual_y5.columns if datas_manual_y5[col].nunique() == 1]\n",
    "    datas_manual_y5.drop(columns=single_value_columns, inplace=True)\n",
    "\n",
    "    # 缺失值处理\n",
    "    datas_manual_y5 = datas_manual_y5[datas_manual_y5.isnull().mean(axis=1) <= 0.70]\n",
    "    datas_manual_y5 = datas_manual_y5.loc[:, datas_manual_y5.isnull().mean() <= 0.70]\n",
    "\n",
    "    # 中位数填充\n",
    "    numeric_columns = datas_manual_y5.select_dtypes(include=[np.number]).columns\n",
    "    for col in numeric_columns:\n",
    "        median_value = datas_manual_y5[col].median()\n",
    "        datas_manual_y5[col] = datas_manual_y5[col].fillna(median_value)\n",
    "\n",
    "    return datas_manual_y5, datas_manual_y6\n",
    "\n",
    "# --------------------------\n",
    "# 2. 特征工程\n",
    "# --------------------------\n",
    "def feature_engineering(X_train, X_test, y_train):\n",
    "    # 标准化\n",
    "    scaler = StandardScaler()\n",
    "    X_train_scaled = scaler.fit_transform(X_train)\n",
    "    X_test_scaled = scaler.transform(X_test)\n",
    "    X_train_scaled = pd.DataFrame(X_train_scaled, index=X_train.index, columns=X_train.columns)\n",
    "    X_test_scaled = pd.DataFrame(X_test_scaled, index=X_test.index, columns=X_test.columns)\n",
    "\n",
    "    # 异常值处理（使用3σ原则）\n",
    "    def cap_outliers(df, train_df):\n",
    "        for col in df.columns:\n",
    "            mean = train_df[col].mean()\n",
    "            std = train_df[col].std()\n",
    "            upper = mean + 3 * std\n",
    "            lower = mean - 3 * std\n",
    "            df[col] = df[col].clip(lower, upper)\n",
    "        return df\n",
    "\n",
    "    X_train_clean = cap_outliers(X_train_scaled, X_train_scaled)\n",
    "    X_test_clean = cap_outliers(X_test_scaled, X_train_scaled)\n",
    "\n",
    "    # 特征选择：互信息\n",
    "    mi = mutual_info_classif(X_train_clean, y_train)\n",
    "    mi_series = pd.Series(mi, index=X_train_clean.columns).sort_values(ascending=False)\n",
    "    top_k = max(5, np.argmax(mi_series.cumsum() / mi_series.sum() >= 0.8) + 1)\n",
    "    selector = SelectKBest(mutual_info_classif, k=top_k)\n",
    "    X_train_selected = selector.fit_transform(X_train_clean, y_train)\n",
    "    X_test_selected = selector.transform(X_test_clean)\n",
    "    selected_cols = X_train_clean.columns[selector.get_support()]\n",
    "\n",
    "    # 去除高VIF特征\n",
    "    def remove_high_vif(df, threshold=10):\n",
    "        while True:\n",
    "            vif = [variance_inflation_factor(df.values, i) for i in range(df.shape[1])]\n",
    "            max_vif = max(vif)\n",
    "            if max_vif > threshold:\n",
    "                idx = vif.index(max_vif)\n",
    "                col = df.columns[idx]\n",
    "                df = df.drop(columns=col)\n",
    "            else:\n",
    "                break\n",
    "        return df\n",
    "\n",
    "    X_train_vif = remove_high_vif(pd.DataFrame(X_train_selected, columns=selected_cols))\n",
    "    X_test_vif = pd.DataFrame(X_test_selected, columns=selected_cols)[X_train_vif.columns]\n",
    "\n",
    "    return X_train_vif, X_test_vif, scaler, selector\n",
    "\n",
    "# --------------------------\n",
    "# 3. 模型训练与评估\n",
    "# --------------------------\n",
    "def train_and_evaluate(X_train, X_test, y_train, y_test):\n",
    "    # 采样\n",
    "    sampler = SMOTETomek(random_state=42)\n",
    "    X_resampled, y_resampled = sampler.fit_resample(X_train, y_train)\n",
    "\n",
    "    # 训练模型\n",
    "    models = {\n",
    "        'RandomForest': RandomForestClassifier(class_weight='balanced', random_state=42),\n",
    "        'XGBoost': xgb.XGBClassifier(scale_pos_weight=(y_train == 0).sum() / (y_train == 1).sum(), random_state=42),\n",
    "        'BalancedRF': BalancedRandomForestClassifier(random_state=42)\n",
    "    }\n",
    "\n",
    "    for name, model in models.items():\n",
    "        model.fit(X_resampled, y_resampled)\n",
    "        y_pred = model.predict(X_test)\n",
    "        print(f\"\\n{name} 分类报告:\")\n",
    "        print(classification_report(y_test, y_pred))\n",
    "        print(f\"AUC: {roc_auc_score(y_test, model.predict_proba(X_test)[:, 1]):.4f}\")\n",
    "\n",
    "    return models\n",
    "\n",
    "# --------------------------\n",
    "# 4. 主流程\n",
    "# --------------------------\n",
    "def main():\n",
    "    print(\"加载数据...\")\n",
    "    data_y5, data_y6 = load_and_preprocess()\n",
    "    \n",
    "    X = data_y5.drop(columns=['LABEL'])  # 假设最后一列是标签\n",
    "    y = data_y5['LABEL']\n",
    "    \n",
    "    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)\n",
    "    \n",
    "    print(\"特征工程...\")\n",
    "    X_train_fe, X_test_fe, scaler, selector = feature_engineering(X_train, X_test, y_train)\n",
    "    \n",
    "    print(\"训练模型...\")\n",
    "    models = train_and_evaluate(X_train_fe, X_test_fe, y_train, y_test)\n",
    "    \n",
    "    # 保存最佳模型（以XGBoost为例）\n",
    "    joblib.dump(models['XGBoost'], 'best_model.pkl')\n",
    "    joblib.dump(scaler, 'scaler.pkl')\n",
    "    joblib.dump(selector, 'selector.pkl')\n",
    "    print(\"模型已保存\")\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ddbe85e-581c-4443-b89b-e052d82a0f1e",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
